video_player.js 12.8 KB
Newer Older
Piotr Mitros committed
1 2
// Things to abstract out to another file

3 4 5
// We do sync AJAX for just the page close event. 
// TODO: This should _really_ not be a global. 
var log_close_event = false; 
Piotr Mitros committed
6 7

function log_close() {
8 9
    var d=new Date();
    var t=d.getTime();
10 11
    //close_event_logged = "waiting";
    log_close_event = true;
Piotr Mitros committed
12
    log_event('page_close', {});
13
    log_close_event = false;
Piotr Mitros committed
14 15
    // Google Chrome will close without letting the event go through.
    // This causes the page close to be delayed until we've hit the
16
    // server. The code below fixes it, but breaks Firefox. 
17
    // TODO: Check what happens with no network. 
18
    /*while((close_event_logged != "done") && (d.getTime() < t+500)) {
19
	console.log(close_event_logged);
20
    }*/
Piotr Mitros committed
21 22 23 24
}

window.onbeforeunload = log_close;

Piotr Mitros committed
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
function getCookie(name) {
    var cookieValue = null;
    if (document.cookie && document.cookie != '') {
	var cookies = document.cookie.split(';');
	for (var i = 0; i < cookies.length; i++) {
	    var cookie = jQuery.trim(cookies[i]);
	    // Does this cookie string begin with the name we want?
	    if (cookie.substring(0, name.length + 1) == (name + '=')) {
		cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
		break;
	    }
	}
    }
    return cookieValue;
}

function postJSON(url, data, callback) {
    $.ajax({type:'POST',
	    url: url,
		dataType: 'json',
45
		data: data,
Piotr Mitros committed
46 47
		success: callback,
		headers : {'X-CSRFToken':getCookie('csrftoken')}
Piotr Mitros committed
48 49 50
  });
}

51 52 53 54
function postJSONAsync(url, data, callback) {
    $.ajax({type:'POST',
	    url: url,
		dataType: 'json',
55
		data: data,
56 57 58 59 60 61
		success: callback,
		headers : {'X-CSRFToken':getCookie('csrftoken')},
		async:true
		});
}

62 63 64 65 66 67 68 69 70 71 72 73 74 75
// For easy embedding of CSRF in forms
$(function() {
    $('#csrfmiddlewaretoken').attr("value", getCookie('csrftoken'))
});

// For working with circuits in wiki: 

function submit_circuit(circuit_id) {
    $("input.schematic").each(function(index,element){ element.schematic.update_value(); });
    postJSON('/save_circuit/'+circuit_id, 
	     {'schematic': $('#schematic_'+circuit_id).attr("value")}, 
	     function(data){ if (data.results=='success') alert("Saved");});
    return false;
}
Piotr Mitros committed
76 77 78 79

// Video player

var load_id = 0;
80 81
var caption_id;
var video_speed = "1.0";
Piotr Mitros committed
82

83 84 85
var updateytPlayerInterval;
var ajax_videoInterval;

Piotr Mitros committed
86 87 88
function change_video_speed(speed, youtube_id) {
    new_position = ytplayer.getCurrentTime() * video_speed / speed;
    video_speed = speed;
89 90
    ytplayer.loadVideoById(youtube_id, new_position);
    syncPlayButton();
91
    log_event("speed", {"new_speed":speed, "clip":youtube_id});
92 93

    $.cookie("video_speed", speed, {'expires':3650, 'path':'/'});
Piotr Mitros committed
94 95 96 97
}

function caption_at(index) {
    if (captions==0)
98
	return "";
Piotr Mitros committed
99 100 101 102

    text_array=captions.text

    if ((index>=text_array.length) || (index < 0))
103
	return "";
Piotr Mitros committed
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
    return text_array[index];
}

function caption_time_at(index) {
    if (captions==0)
	return 0;

    time_array=captions.start;

    if (index < 0)
	return 0;
    if (index>=time_array.length)
	return ytplayer.getDuration();

    return time_array[index] / 1000.0 / video_speed;
}

function caption_index(now) {
    // Returns the index of the current caption, given a time
    now = now * video_speed;

    if (captions==0)
	return 0;

    time_array=captions.start

    // TODO: Bisection would be better, or something incremental
    var i; 
    for(i=0;i<captions.start.length; i++) {
	if(time_array[i]>(now*1000)) {
	    return i-1;
	}
    }
    return i-1;
}

140 141
function format_time(t)
{
142 143 144
    seconds = Math.floor(t);
    minutes = Math.floor(seconds / 60);
    hours = Math.floor(minutes / 60);
145 146
    seconds = seconds % 60;
    minutes = minutes % 60;
147 148 149 150 151 152

    if (hours) {
      return hours+":"+((minutes < 10)?"0":"")+minutes+":"+((seconds < 10)?"0":"")+(seconds%60);
    } else {
      return minutes+":"+((seconds < 10)?"0":"")+(seconds%60);
    }
153 154
}

Piotr Mitros committed
155 156
function update_captions(t) {
    var i=caption_index(t);
157
    $("#vidtime").html(format_time(ytplayer.getCurrentTime())+' / '+format_time(ytplayer.getDuration()));
158 159 160 161 162
    var j;
    for(j=1; j<9; j++) {
	$("#std_n"+j).html(caption_at(i-j));
	$("#std_p"+j).html(caption_at(i+j));
    }
Piotr Mitros committed
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
    $("#std_0").html(caption_at(i));
}

function title_seek(i) {
    // Seek video forwards or backwards by i subtitles
    current=caption_index(getCurrentTime());
    new_time=caption_time_at(current+i);
    
    ytplayer.seekTo(new_time, true);
}

function updateHTML(elmId, value) {
    document.getElementById(elmId).innerHTML = value;
}

function setytplayerState(newState) {
    //    updateHTML("playerstate", newState);
}

// Updates server with location in video so we can resume from the same place
// IMPORTANT TODO: Load test
// POSSIBLE FIX: Move to unload() event and similar
var ajax_video=function(){};
186
var ytplayer;
Piotr Mitros committed
187 188 189

function onYouTubePlayerReady(playerId) {
    ytplayer = document.getElementById("myytplayer");
190 191
    updateytplayerInfoInterval = setInterval(updateytplayerInfo, 500);
    ajax_videoInterval = setInterval(ajax_video,5000);
Piotr Mitros committed
192 193 194 195
    ytplayer.addEventListener("onStateChange", "onytplayerStateChange");
    ytplayer.addEventListener("onError", "onPlayerError");
    if((typeof load_id != "undefined") && (load_id != 0)) {
	var id=load_id;
196
	loadNewVideo(caption_id, id, 0);
Piotr Mitros committed
197
    }
198 199 200 201 202 203 204 205 206 207
}

/* HTML5 YouTube iFrame API Specific */
function onYouTubePlayerAPIReady() {
  ytplayer = new YT.Player('html5_player', {
    events: { 
      'onReady': onPlayerReady,
      'onStateChange': onPlayerStateChange
    }
  });
208
  updateytplayerInfoInterval = setInterval(updateHTML5ytplayerInfo, 200);
209
  //ajax_videoInterval = setInterval(ajax_video, 5000);
210 211
}

212 213 214 215 216 217 218 219
// Need this function to call the API ready callback when we switch to a tab with AJAX that has a video
// That callback is not being fired when we switch tabs. 
function loadHTML5Video() {
    if (!ytplayer && switched_tab){
      onYouTubePlayerAPIReady();
    }
}

220 221 222 223 224 225 226 227 228 229 230 231 232 233
function isiOSDevice(){
  var iphone = "iphone";
  var ipod = "ipod";
  var ipad = "ipad";
  var uagent = navigator.userAgent.toLowerCase();

  //alert(uagent);
  if (uagent.search(ipad) > -1 || uagent.search(iphone) > -1
      || uagent.search(ipod) > -1) {
    return true;
  }
  return false;
}

234
function onPlayerReady(event) {
235 236 237 238 239
  //do not want to autoplay on iOS devices since its not enabled
  //and leads to confusing behavior for the user
  if (!isiOSDevice()) {
    event.target.playVideo();
  }
240
}
Piotr Mitros committed
241

242 243 244
function onPlayerStateChange(event) {
  if (event.data == YT.PlayerState.PLAYING) {
  }
Piotr Mitros committed
245 246
}

247 248
/* End HTML5 Specific */

249 250

var switched_tab = false; // switch to true when we destroy so we know to call onYouTubePlayerAPIReady()
251
// clear pings to video status when we switch to a different sequence tab with ajax
252
function videoDestroy(id) {
Piotr Mitros committed
253 254
//    postJSON('/modx/video/'+id+'/goto_position',
//	     {'position' :  ytplayer.getCurrentTime()});
255

Piotr Mitros committed
256
    load_id = 0;
257 258
    clearInterval(updateytplayerInfoInterval);
    clearInterval(ajax_videoInterval);
Piotr Mitros committed
259
    ytplayer = false;
260
    switched_tab = true;
Piotr Mitros committed
261 262 263
}

function log_event(e, d) {
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
    data = {
	"event_type" : e, 
	"event" : JSON.stringify(d),
	"page" : document.URL
    }
    $.ajax({type:'GET',
	    url: '/event',
	    dataType: 'json',
	    data: data,
	    async: !log_close_event, // HACK: See comment on log_close_event
	    success: function(){},
	    headers : {'X-CSRFToken':getCookie('csrftoken')}
	   });

    /*, // Commenting out Chrome bug fix, since it breaks FF
Piotr Mitros committed
279
	  function(data) {
280
	      console.log("closing");
Piotr Mitros committed
281
	      if (close_event_logged == "waiting") {
282 283
		  close_event_logged = "done";
	      console.log("closed");
Piotr Mitros committed
284
	  }
285
	  });*/
Piotr Mitros committed
286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325
}

function seek_slide(type,oe,value) {
    //log_event('video', [type, value]);
    if(type=='slide') {
	 // HACK/TODO: Youtube recommends this be false for slide and true for stop.
	 // Works better on my system with true/true. 
	 // We should test both configurations on low/high bandwidth 
	 // connections, and different browsers
	 // One issue is that we query the Youtube window every 250ms for position/state
	 // With false, it returns the old one (ignoring the new seek), and moves the
         // scroll bar to the wrong spot. 
	ytplayer.seekTo(value, true);
    } else if (type=='stop') {
	ytplayer.seekTo(value, true);
	log_event('video', [type, value]);
    }

    update_captions(value);
}

function get_state() {
    if (ytplayer)
	return [ytplayer.getPlayerState(),
		ytplayer.getVideoUrl(),
		ytplayer.getDuration(), ytplayer.getCurrentTime(), 
		ytplayer.getVideoBytesLoaded(), ytplayer.getVideoBytesTotal(), 
		ytplayer.getVideoStartBytes(), 
		ytplayer.getVolume(),ytplayer.isMuted(),
		ytplayer.getPlaybackQuality(),
		ytplayer.getAvailableQualityLevels()];
    return [];
}

function onytplayerStateChange(newState) {
    setytplayerState(newState);
    log_event('video', ['State Change',newState, get_state()]);
}

function onPlayerError(errorCode) {
Piotr Mitros committed
326
    //    alert("An error occured: " + errorCode);
327
    log_event("player_error", {"error":errorCode});
Piotr Mitros committed
328 329
}

330 331 332 333 334 335 336
// Currently duplicated to check for if video control changed by clicking the video for HTML5
// Hacky b/c of lack of control over YT player
function updateHTML5ytplayerInfo() {
    var player_state = getPlayerState();
    if(player_state != 3) {
      $("#slider").slider("option","max",ytplayer.getDuration());
      $("#slider").slider("option","value",ytplayer.getCurrentTime());
Piotr Mitros committed
337
    }
338 339
    if (player_state == 1){
      update_captions(getCurrentTime());
Piotr Mitros committed
340
    }
341 342 343 344 345
    if (player_state == 1 && $("#video_control").hasClass("play"))
      $("#video_control").removeClass().addClass("pause");
    else if (player_state == 2 && $("#video_control").hasClass("pause"))
      $("#video_control").removeClass().addClass("play");
}
Piotr Mitros committed
346

347 348 349 350 351 352 353 354
function updateytplayerInfo() {
    var player_state = getPlayerState();
    if(player_state != 3) {
      $("#slider").slider("option","max",ytplayer.getDuration());
      $("#slider").slider("option","value",ytplayer.getCurrentTime());
    }
    if (player_state == 1){
      update_captions(getCurrentTime());
Kyle Fiedler committed
355
      handle = $('.ui-slider-handle',  $('#slider'));
356
      handle.qtip('option', 'content.text', '' +  format_time(getCurrentTime()));
357
    }
358
       // updateHTML("videoduration", getDuration());
Piotr Mitros committed
359 360 361 362 363 364
    //    updateHTML("videotime", getCurrentTime());
    //    updateHTML("startbytes", getStartBytes());
    //    updateHTML("volume", getVolume());
}

// functions for the api calls
365
function loadNewVideo(cap_id, id, startSeconds) {
Piotr Mitros committed
366
    captions={"start":[0],"end":[0],"text":["Attempting to load captions..."]};
367
    $.getJSON("/static/subs/"+cap_id+".srt.sjson", function(data) {
Piotr Mitros committed
368 369
        captions=data;
    });
370
    caption_id = cap_id;
Piotr Mitros committed
371 372 373 374 375 376 377 378 379 380 381
    load_id = id;
    //if ((typeof ytplayer != "undefined") && (ytplayer.type=="application/x-shockwave-flash")) {
    // Try it every time. If we fail, we want the error message for now. 
    // TODO: Add try/catch
    try {
	ytplayer.loadVideoById(id, parseInt(startSeconds));
        load_id=0;
    }
    catch(e) {
	window['console'].log(JSON.stringify(e));
    }
382
    log_event("load_video", {"id":id,"start":startSeconds});
Piotr Mitros committed
383
    //$("#slider").slider("option","value",startSeconds);
384
    //seekTo(startSeconds);
Piotr Mitros committed
385 386
}

387 388 389
function syncPlayButton(){
  var state = getPlayerState();
  if (state == 1 || state == 3) {
390
    $("#video_control").removeClass("play").addClass("pause");
391
  } else if (state == 2 || state == -1 || state == 0){
392
    $("#video_control").removeClass("pause").addClass("play");
393 394 395
  }
}

Piotr Mitros committed
396 397 398 399 400 401 402 403 404 405
function cueNewVideo(id, startSeconds) {
    if (ytplayer) {
	ytplayer.cueVideoById(id, startSeconds);
    }
}

function play() {
    if (ytplayer) {
	ytplayer.playVideo();
    }
406
    log_event("play_video", {"id":getCurrentTime(), "code":getEmbedCode()});
Piotr Mitros committed
407 408 409 410 411 412
}

function pause() {
    if (ytplayer) {
	ytplayer.pauseVideo();
    }
413
    log_event("pause_video", {"id":getCurrentTime(), "code":getEmbedCode()});
Piotr Mitros committed
414 415 416 417 418 419
}

function stop() {
    if (ytplayer) {
	ytplayer.stopVideo();
    }
420
    log_event("stop_video", {"id":getCurrentTime(), "code":getEmbedCode()});
Piotr Mitros committed
421 422 423 424
}

function getPlayerState() {
    if (ytplayer) {
Kyle Fiedler committed
425
      return ytplayer.getPlayerState();
Piotr Mitros committed
426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471
    }
}

function seekTo(seconds) {
    if (ytplayer) {
	ytplayer.seekTo(seconds, true);
    }
}

function getBytesTotal() {
    if (ytplayer) {
	return ytplayer.getVideoBytesTotal();
    }
}

function getCurrentTime() {
    if (ytplayer) {
	return ytplayer.getCurrentTime();
    }
}

function getDuration() {
    if (ytplayer) {
	return ytplayer.getDuration();
    }
}

function getStartBytes() {
    if (ytplayer) {
	return ytplayer.getVideoStartBytes();
    }
}

function mute() {
    if (ytplayer) {
	ytplayer.mute();
    }
}

function unMute() {
    if (ytplayer) {
	ytplayer.unMute();
    }
}

function getEmbedCode() {
472 473 474
    if(ytplayer) {
	ytplayer.getVideoEmbedCode();
    }
Piotr Mitros committed
475 476 477
}

function getVideoUrl() {
478 479 480
    if(ytplayer) {
	ytplayer.getVideoUrl();
    }
Piotr Mitros committed
481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498
}

function setVolume(newVolume) {
    if (ytplayer) {
	ytplayer.setVolume(newVolume);
    }
}

function getVolume() {
    if (ytplayer) {
	return ytplayer.getVolume();
    }
}

function clearVideo() {
    if (ytplayer) {
	ytplayer.clearVideo();
    }
499
}