var rand = Math.random;
var iter = setInterval;
var DEBUG = false;
var markTimestamp = -1;
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
var all = null;
var loopHandles = [];
var jumpTimestamp = 0;
var row = 4;
var col = 4;
var cellWidth = 50;
var cellHeight = 50;
var targetVideos = [];
var initialLoading = true;
var youtubeid = "";
var gridVideos = [];
var YTSTATE_UNSTARTED = -1;
var YTSTATE_ENDED = 0;
var YTSTATE_PLAYING = 1;
var YTSTATE_PAUSED = 2;
var YTSTATE_BUFFERING = 3;
var YTSTATE_VIDEOCUED = 5;
function onYouTubeIframeAPIReady() {
if(DEBUG)console.log("is ready");
}
function onPlayerReady(event) {
var i = parseInt(event.target.getIframe().getAttribute("row"))
var j = parseInt(event.target.getIframe().getAttribute("col"))
if(!gridVideos[i]) gridVideos[i] = [];
gridVideos[i][j] = event.target;
function initialize(){
if(event.target.mute == undefined){
setTimeout(initialize,10);
return;
}
event.target.mute()
event.target.seekTo(0);
}
event.target.initialized = true;
initialize();
}
// 5. The API calls this function when the player's state changes.
// The function indicates that when playing a video (state=1),
// the player should play for six seconds and then stop.
var done = false;
function parseYTState(num){
if( num == -1) return "unstarted";
if( num == 0) return "ended";
if( num == 1) return "playing";
if( num == 2) return "paused";
if( num == 3) return "buffering";
if( num == 5) return "video cued";
return "unknown";
}
function setMark(){
markTimestamp = (new Date()).getTime();
}
// run the function when the document is ready
$(document).ready(function () {
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
lineNumbers: false,
styleActiveLine: true,
matchBrackets: true,
height: 'auto',
mode:{name: "javascript", json: true}
});
// jquery ui
$( "#resizable" ).resizable();
$( "#resizable" ).draggable();
var livecode = function(cm){
var doc = cm.getDoc();
var code = doc.getSelection();
if(code.length > 0){ // when there is any selected text
if(DEBUG)console.log(code);
try {
if(code.includes("setInterval")){
var sure;
if(code.substring(0,10)== "setInterval"){
sure = confirm("Are you sure you that want setInterval without a handle?");
if(!sure)return;
}
if(!code.includes("clearInterval")){
var sure = confirm("Are you sure? This does not have clearInterval. ");
if(!sure) return;
}
}
eval(code);
_.defer(function(){
var start = doc.getCursor("anchor");
var end = doc.getCursor("head");
if(start.line > end.line || (start.line == end.line && start.ch > end.ch)){
var temp = start;
start = end;
end = temp;
}
var obj = doc.markText(start,end,{className:"ex-high"});
setTimeout(function(){
_.defer(function(){
obj.clear();
});
},100);
});
} catch (e) {
alert(e.message);
console.error(e);
}
}else{ // when there is no selectino, evaluate the line where the cursor is
code = doc.getLine(cm.getDoc().getCursor().line);
if(DEBUG)console.log(code);
try {
eval(code);
_.defer(function(){
var start = doc.getCursor();
var obj = doc.markText({line:start.line, ch:0},{line:start.line, ch:code.length},{className:"ex-high"});
setTimeout(function(){
_.defer(function(){
obj.clear();
});
},100);
});
} catch (e) {
alert(e.message);
console.error(e);
}
}
};
var map = {"Shift-Enter": livecode};
var showHelp = function(cm){
var code = cm.getDoc().getSelection();
if (code.length <= 0){ // when there is any selected text
code = cm.getDoc().getLine(cm.getDoc().getCursor().line);
}
var index = code.indexOf('(');
var fName = code.substring(0, index);
console.log(fName);
help(fName);
};
var map = {
"Shift-Enter": livecode,
"Alt-Enter": showHelp
};
editor.addKeyMap(map);
$("#youtube-result").hide();
$(window).keydown(function(e){
if (e.ctrlKey){
$("#code-container").toggle();
$(".go-back-editor").toggle();
}
});
$(".go-back-button").click(function(){
$("#code-container").toggle();
$(".go-back-editor").toggle();
});
});
/**
* Add columns and rows to the grid
* @param {integer} addRow - number of rows to add.
* @param {integer} addCol - number of columns to add.
* @param {string} id - YouTube identifier.
*/
function add(addRow,addCol,id){
initialLoading = true;
var row = gridVideos.length + addRow;
var col = addCol;
if(gridVideos[0])
col += gridVideos[0].length;
if(id)
youtubeid = id;
if(row <0 || col < 0){
alert("Row col value negatives.!");
return;
}
var rowHeight = 12/row;
var colWidth = 12/col;
if(12%row!=0 || 12%col!=0){
alert("we can only take a divisor of 12.");
return;
}
var divrowclass = 'row-xs-'+rowHeight;
var divcolclass = 'yt-cell col-sm-'+colWidth+' col-md-'+colWidth+' col-lg-'+colWidth+' col-xs-'+colWidth;
var divrowhtml = '<div class='+divrowclass+'">'
var divcolhtml = '<div class = "'+divcolclass+'"></div>'
// let's add the videos
cellWidth = $(document).width() / col;
cellHeight = $(document).height() / row;
// let's resize the existing divs in gridstack.
for (var i=0; i < gridVideos.length; i++){
divRowGrid[i].removeClass();
divRowGrid[i].addClass(divrowclass);
for (var j=0; j< gridVideos[0].length; j++){
$(gridVideos[i][j].getIframe()).removeClass().addClass(divcolclass);
gridVideos[i][j].setSize(cellWidth,cellHeight);
/* var stateDV = $("#state-div-"+ i +"-"+ j);
stateDV.removeClass();
stateDV.addClass(divrowclass);
$("#state-div-"+ i +"-"+ j).width(cellWidth);
$("#state-div-"+ i +"-"+ j).height(cellHeight);*/
}
}
// add cols first
for (var i=0; i <gridVideos.length; i++){
if(!gridVideos[0])
gridVideos = [];
for (var j= gridVideos[0].length; j< col; j++){
numLoadingVideo++;
var dcol = $(divcolhtml);
dcol.attr("id","cell-"+ i +"-"+ j);
dcol.attr("row",i);
dcol.attr("col",j);
divRowGrid[i].append(dcol)
if(id)addVideo(i,j);
}
}
// add rows first
for (var i=gridVideos.length; i <row; i++){
var ddiv = $(divrowhtml);
var ddiv_state = $(divrowhtml);
$("#youtubegrid").append(ddiv);
for (var j= 0; j< col; j++){
numLoadingVideo++;
var dcol = $(divcolhtml);
dcol.attr("id","cell-"+ i +"-"+ j);
dcol.attr("row",i);
dcol.attr("col",j);
ddiv.append(dcol)
if(id)addVideo(i,j);
}
divRowGrid[i] = ddiv;
}
}
/**
* Create a grid
* @param {integer} row - number of rows to create.
* @param {integer} col - number of columns to create.
* @param {string} id - YouTube identifier.
*/
function create(row,col,id){
youtubeid = id;
initialLoading = true;
divRowGrid = [];
gridVideos = [];
unloop();
$("#youtubegrid").empty();
$("#youtubegrid-state").empty();
targetVideos = [];
if(12%row!=0 || 12%col!=0){
alert("we can only take a divisor of 12.");
return
}
numLoadingVideo = row * col;
var rowHeight = 12/row;
var colWidth = 12/col;
var divrowhtml = '<div class="row-xs-'+rowHeight+'">'
var divcolhtml = '<div class = "yt-cell col-sm-'+colWidth+' col-md-'+colWidth+' col-lg-'+colWidth+' col-xs-'+colWidth+'"></div>'
var spanhtml = '<span class = "player_state">state</span>'
for (var i=0; i<row; i++){
var ddiv = $(divrowhtml);
var ddiv_state = $(divrowhtml);
for (var j=0; j<col; j++){
var dcol = $(divcolhtml);
var dcol_state = $(divcolhtml);
dcol.appendTo(ddiv);
dcol.attr("id","cell-"+ i +"-"+ j);
dcol.attr("row",i);
dcol.attr("col",j);
divRowGrid[i] = ddiv;
if(DEBUG){
dcol_state.addClass("div_state");
dcol_state.attr("id","state-div-"+ i +"-"+ j);
var spanElem = $(spanhtml);
spanElem.attr("id","state-cell-"+ i +"-"+ j);
spanElem.appendTo(dcol_state);
dcol_state.appendTo(ddiv_state);
}
}
$("#youtubegrid").append(ddiv);
if(DEBUG)$("#youtubegrid-state").append(ddiv_state);
if(!DEBUG) $("#youtubegrid-state").remove();
}
cellHeight = ddiv.height();
cellWidth = dcol.width();
for ( i=0; i< row; i++){
for ( j=0 ; j<col; j++){
addVideo(i,j);
}
}
}
function addVideo(i,j){
var player = new YT.Player("cell-"+ i +"-"+ j, {
height: cellHeight,
width: cellWidth,
videoId: youtubeid,
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange
},
suggestedQuality:"small"
});
}
var searchResult = [];
/**
* Search YouTube. Selecto item on the right of the screen to get the YouTube identifier text.
* @param {string} query - Query to search.
*/
function search(query) {
$("#youtube-result").show();
$('#youtube-result').empty();
url = 'https://www.googleapis.com/youtube/v3/search';
var params = {
part: 'snippet',
key: 'AIzaSyDAKDaBy_JDwcScSHqDQimOOLjdPImLanc', // github gist에서 본 api_token 이라서 새로 하나 받아야 할 것 같아요.
q: query,
type: "video",
maxResults: 20,
videoEmbeddable :"true",
videoLicense:"youtube"
};
$.getJSON(url, params, function (query) {
searchResult = query.items
searchResult.forEach(function(entry) {
if(DEBUG)console.log(entry.snippet.title); // 화면에 출력해보려고 했는데, codemirror에 output은 어떻게 하는지 잘 모르겠네요.
console.log(entry.snippet.title); // 화면에 출력해보려고 했는데, codemirror에 output은 어떻게 하는지 잘 모르겠네요.
title = entry.snippet.title;
thumburl = entry.snippet.thumbnails.default.url;
thumbimg = '<img class="thumb-img" src="'+thumburl+'">';
$('#youtube-result').append('<div class = "thumb" id="yt-r-' +entry.id.videoId+ '" yt-id="' +entry.id.videoId+ '"><div>' + thumbimg +'</div><div class = "thumb-title" >'+ title + '</div>');
// $("#youtube-result").append(entry.snippet.title + ",<span id=yt-r-" +entry.id.videoId+ " yt-id=" +entry.id.videoId+ ">" + entry.id.videoId + "</span><br>")
$("#yt-r-" +entry.id.videoId).click(function(){
updateCodeMirror("\"" + entry.id.videoId + "\"");
});
});
});
}
function updateCodeMirror(data){
var doc = $('.CodeMirror')[0].CodeMirror.getDoc();
doc.replaceSelection(data); // adds a new line
$("#youtube-result").hide();
$('.CodeMirror')[0].CodeMirror.focus();
}
/**
* Change playback speed of the selected videos.
* @param {select} videos - See [how to select videos]{@link _howToSelectVideos}.
* @param {float} newSpeed - New speed.
*/
function speed(list, newSpeed) {
var selectedVideos = selectVideos(list);
selectedVideos.forEach(function(video){
video.setPlaybackRate(newSpeed)
});
}
/**
* mute/unMute the selected videos.
* @param {select} videos - See [how to select videos]{@link _howToSelectVideos}.
* @param {bool} mute - true = mute / false = unMute.
*/
function mute(list, mute) {
var selectedVideos = selectVideos(list);
selectedVideos.forEach(function(video){
if (mute)
video.mute()
else
video.unMute()
});
}
/**
* Set volume of the selected videos.
* @param {select} videos - See [how to select videos]{@link _howToSelectVideos}.
* @param {integer} vol - New volume. (0 ~ 100)
*/
function volume(list,vol) {
var selectedVideos = selectVideos(list);
selectedVideos.forEach(function(video){
video.setVolume(vol);
});
}
/**
* Increase (or decrease) volume of the selected videos.
* @param {select} videos - See [how to select videos]{@link _howToSelectVideos}.
* @param {integer} diff - To decrease volume, pass negative number.
*/
function turnup(list, diff) {
var selectedVideos = selectVideos(list);
selectedVideos.forEach(function(video){
var newVolume = video.getVolume() + diff
video.setVolume(newVolume)
});
}
//function alternate(list, )
/**
* Replace the selected videos with id.
* @param {select} videos - See [how to select videos]{@link _howToSelectVideos}.
* @param {string} id - YouTube identifier.
* @param {bool} cancelloop - To cancel the loop that may have been set earlier.
*/
function cue(list, id, cancelloop) {
if(!cancelloop) cancelloop = true;
var selectedVideos = selectVideos(list);
selectedVideos.forEach(function(video){
video.cueVideoById(id)
video.initialized = true;
video.mute();
video.playVideo();
video.setPlaybackRate(1);
event.target.initialized = true;
if(video.loopHandle && cancelloop){
clearInterval(video.loopHandle);
}
if(video.here)video.here = null;
});
}
function fadeInInner(video, diff) {
var currentVolume = video.getVolume();
// console.log("cur: " + currentVolume + "/ diff: "+diff);
if (currentVolume < 100) {
video.setVolume(currentVolume + diff);
return setTimeout((function() {
return fadeInInner(video, diff);
}), 100);
}
}
/**
* Start playing the selected videos with increasing volume.
* @param {select} videos - See [how to select videos]{@link _howToSelectVideos}.
* @param {integer} duration - Duration of time in seconds.
*/
function fadeIn(list,duration) {
if(!duration) duration = 5;
var diff = 10.0 / duration;
var selectedVideos = selectVideos(list);
selectedVideos.forEach(function(v){
v.setVolume(0);
if(v.getPlayerState() != YTSTATE_PLAYING)
v.playVideo();
});
selectedVideos.forEach(function(v){
fadeInInner(v, diff);
});
}
function fadeOutInner(video, diff) {
var currentVolume = video.getVolume();
if (currentVolume > 0) {
video.setVolume(currentVolume - diff);
return setTimeout((function() {
return fadeOutInner(video, diff);
}), 100);
}
}
/**
* Fade out the volume of selected videos.
* @param {select} videos - See [how to select videos]{@link _howToSelectVideos}.
* @param {integer} duration - Duration of time in seconds.
*/
function fadeOut(list,duration) {
if(!duration) duration = 5;
var diff = 10.0 / duration;
var selectedVideos = selectVideos(list);
selectedVideos.forEach(function(v){
fadeOutInner(v, diff);
});
}
/**
* Returns an array of video index that excludes the specified video index.
* e.g. if the current grid is 3X3.
* not(1) returns [0,2,3,4,5,6,7,8]
* e.g. if the current grid is 4X4.
* not(8,7) returns [0,1,2,3,4,5,6,9,10,11,12,13,14,15]
* @param {integer[]} indices - Indices to exclude.
*/
function not() {
var list = [...Array(targetVideos.length).keys()];
for (var i = 0; i < arguments.length; i++) {
target = arguments[i];
index = list.indexOf(target);
if (index > -1) {
list.splice(index, 1);
}
}
return list;
}
function selectVideos(list){
var selectedVideos = []
if (typeof(list) == "string") {
var condition = list;
var selectedVideos = [];
for (var i = 0; i < targetVideos.length; i++) {
if (eval(i + condition))
selectedVideos.push(targetVideos[i]);
}
return selectedVideos;
}
else if (list === parseInt(list, 10) ){
selectedVideos.push(targetVideos[list])
}
else if (list == null) {
selectedVideos = targetVideos
}
else if (list.length > 1) {
for(var i=0; i< list.length; i++){
var index = list[i];
selectedVideos.push(targetVideos[index]);
}
}
else{
alert("ERROR: edge case found", list);
}
return selectedVideos;
}
/**
* Phase
* @param {select} videos - See [how to select videos]{@link _howToSelectVideos}.
* @param {integer} interval - interval
*/
function phase(list,interval){ // interval and video id
var selectedVideos = selectVideos(list);
if(interval>=0){
for (var i=1; i<selectedVideos.length; i++){
(function(vindex){
if(DEBUG)console.log("vindex",vindex);
setTimeout(function(){
selectedVideos[vindex].seekTo(selectedVideos[vindex].getCurrentTime()- vindex*interval);
},vindex*interval* 1000);
})(i)
}
return ;
}
// interval < 0
for (var i=selectedVideos.length-2; i>=0; i--){
(function(vindex){
if(DEBUG)console.log("vindex",vindex);
setTimeout(function(){
selectedVideos[vindex].seekTo(selectedVideos[vindex].getCurrentTime()- (selectedVideos.length - vindex-1)*-interval);
},(selectedVideos.length - vindex-1)*-interval* 1000);
})(i)
}
}
/**
* Delay
* @param {select} videos - See [how to select videos]{@link _howToSelectVideos}.
* @param {integer} interval - interval
*/
function delay(list,interval){ // interval and video id
var selectedVideos = selectVideos(list);
if(interval>=0){
for (var i=1; i<selectedVideos.length; i++){
(function(vindex){
if(DEBUG)console.log("vindex",vindex);
setTimeout(function(){
selectedVideos[vindex].playVideo();
},vindex*interval* 1000);
})(i)
selectedVideos[i].pauseVideo(); // do not need to
}
return ;
}
// interval < 0
for (var i=selectedVideos.length-2; i>=0; i--){
(function(vindex){
if(DEBUG)console.log("vindex",vindex);
setTimeout(function(){
selectedVideos[vindex].playVideo();
},(selectedVideos.length - vindex-1)*-interval* 1000);
})(i)
selectedVideos[i].pauseVideo();
}
}
/**
* Sync
* @param {select} videos - See [how to select videos]{@link _howToSelectVideos}.
* @param {integer} index - index
*/
function sync(list, index){
seek(list, targetVideos[index].getCurrentTime());
}
/**
* Pause the selected videos
* @param {select} videos - See [how to select videos]{@link _howToSelectVideos}.
*/
function pause(list){
var selectedVideos = selectVideos(list);
selectedVideos.forEach(function(video){
video.pauseVideo();
});
}
/**
* Play the selected videos
* @param {select} videos - See [how to select videos]{@link _howToSelectVideos}.
* @param {string} quality - small, medium, large, hd720, hd1080, highres, or default.
*/
function setQ(list, quality){
var selectedVideos = selectVideos(list);
selectedVideos.forEach(function(video){
video.setPlaybackQuality(quality);
});
}
/**
* Play the selected videos
* @param {select} videos - See [how to select videos]{@link _howToSelectVideos}.
*/
function play(list){
var selectedVideos = selectVideos(list);
selectedVideos.forEach(function(video){
video.playVideo();
});
}
/**
* Seek to specified time.
* @param {select} videos - See [how to select videos]{@link _howToSelectVideos}.
* @param {integer} seconds - time in seconds.
*/
function seek(list, seconds){
var selectedVideos = selectVideos(list);
selectedVideos.forEach(function(video){
video.seekTo(seconds,true);
});
}
/**
* Loop
* @param {select} videos - See [how to select videos]{@link _howToSelectVideos}.
* @param {integer} back - back
* @param {integer} interval - interval
* @param {integer} phase - phase
*/
function loop(list,back,interval, phase){
var selectedVideos = selectVideos(list);
if(!phase) phase = 0;
/* for (var i=0; selectedVideos.length; i++){
var video = selectedVideos[i];
var atTime = video.getCurrentTime() - back;
video.seekTo(atTime)
if(video.loopHandle){
clearInterval(video.loopHandle);
}
video.loopHandle = setInterval(function(){
video.seekTo(atTime)
},(interval + i * phase)* 1000);
}*/
selectedVideos.forEach(function(video, index){
var atTime = video.getCurrentTime() - back;
video.seekTo(atTime)
if(video.loopHandle){
clearInterval(video.loopHandle);
}
video.loopHandle = setInterval(function(){
video.seekTo(atTime)
},(interval + index * phase)* 1000);
});
}
function here(list){
var selectedVideos = selectVideos(list);
selectedVideos.forEach(function(video, index){
if(!video.here){
video.here = video.getCurrentTime();
return;
}
// this is problematic
loopAt(list,video.here,video.getCurrentTime() - video.here);
video.here = null;
});
}
/**
* LoopAt
* @param {select} videos - See [how to select videos]{@link _howToSelectVideos}.
* @param {integer} atTime - atTime
* @param {integer} interval - interval
* @param {integer} phase - phase
*/
function loopAt(list,atTime,interval, phase){
var selectedVideos = selectVideos(list);
if(!phase) phase = 0;
/* for (var i=0; selectedVideos.length; i++){
var video = selectedVideos[i];
video.seekTo(atTime)
if(video.loopHandle){
clearInterval(video.loopHandle);
}
(function(v){
v.loopHandle = setInterval(function(){
v.seekTo(atTime)
},(interval + i * phase)* 1000);
})(video);
}*/
selectedVideos.forEach(function(video, index){
video.seekTo(atTime)
if(video.loopHandle){
clearInterval(video.loopHandle);
}
video.loopHandle = setInterval(function(){
video.seekTo(atTime)
},(interval + index * phase)* 1000);
});
}
/**
* Unloop
* @param {select} videos - See [how to select videos]{@link _howToSelectVideos}.
*/
function unloop(list){
var selectedVideos = selectVideos(list);
selectedVideos.forEach(function(video){
if(video.loopHandle){
clearInterval(video.loopHandle);
video.loopHandle = null;
}
});
}
/**
* Jump
* @param {select} videos - See [how to select videos]{@link _howToSelectVideos}.
* @param {integer} num - num
* @param {integer} phase - phase
*/
function jump(list,num,phase){
var selectedVideos = selectVideos(list);
if(phase){
selectedVideos.forEach(function(video){
video.seekTo(video.getCurrentTime() + num,true);
});
}
else{
selectedVideos.forEach(function(video, index){
setTimeout(function(){
video.seekTo(video.getCurrentTime() + num,true);
}, phase * index * 1000)
});
}
}
function onPlayerStateChange(event) {
var now = (new Date()).getTime();
if(DEBUG)$("#state-" + event.target.h.id).text(parseYTState(event.data));
if(DEBUG&&event.data == YTSTATE_PLAYING){
console.log("now - jumpTimestamp:", (now - jumpTimestamp));
}
if(event.target.initialized && event.data == YTSTATE_PLAYING){
event.target.initialized = false;
event.target.pauseVideo();
event.target.seekTo(0);
event.target.unMute()
initialLoading = false;
targetVideos = [];
for(var i = 0; i < gridVideos.length; i++)
{
targetVideos = targetVideos.concat(gridVideos[i]);
}
}
}
function help() {
var path = '/doc/global.html'
if (arguments.length == 1) {
var fName = arguments[0];
path += '#' + fName;
}
console.log(path);
var win = window.open(path, '_blank');
win.focus();
}
/**
* For the most of the methods below, you need to specify which videos to control.
* There are various ways to select videos.
* @param {integer} index - Single index of video
* @param {null|all} all - All the videos
* @param {integer[]} list - Indices of videos
* @param {not(indices)} not - All except indices. See [not()]{@link not}
* @param {string} expression - Condition text (e.g. ">3" or "%2==0")
*/
function _howToSelectVideos() {
}