Commit 73a9c43e by Valera Rozuvan

Addressing Anton's comments on PR 869.

Removed extra check for console.log() existance. Modified error
messages. Formatted entire file to fit in 80 lines or less.

Formatting files so that they fit under 80 lines. Adding debug info for
what type of YouTube video player is being used. Also comment on whether
playbackRates is available for HTML5 mode.

Changed back Anton's original function _checkPlaybackRates(). Now logging for
this function's result is done only on error (in the function that is invoking)
it).
parent 20c16ec6
/** /**
* @file Initialize module works with the JSON config, and sets up various settings, parameters, * @file Initialize module works with the JSON config, and sets up various
* variables. After all setup actions are performed, it invokes the video player to play the * settings, parameters, variables. After all setup actions are performed, it
* specified video. This module must be invoked first. It provides several functions which do not * invokes the video player to play the specified video. This module must be
* fit in with other modules. * invoked first. It provides several functions which do not fit in with other
* modules.
* *
* @external VideoPlayer * @external VideoPlayer
* *
...@@ -16,18 +17,8 @@ define( ...@@ -16,18 +17,8 @@ define(
['video/03_video_player.js'], ['video/03_video_player.js'],
function (VideoPlayer) { function (VideoPlayer) {
// In case if console.log() is unavailable for some reason. // window.console.log() is expected to be available. We do not support
// // browsers which lack this functionality.
// console.log() will be used to put case critical messages to the browser
// JavaScript console to allow for better and quicker debugging.
//
// This is placed here because '01_initialize.js' gets loaded first.
if (typeof window.console === 'undefined') {
window.console = {};
}
if ($.isFunction(window.console.log) === false) {
window.console.log = function () {};
}
// The function gettext() is defined by a vendor library. If, however, it // The function gettext() is defined by a vendor library. If, however, it
// is undefined, it is a simple wrapper. It is used to return a different // is undefined, it is a simple wrapper. It is used to return a different
...@@ -69,43 +60,54 @@ function (VideoPlayer) { ...@@ -69,43 +60,54 @@ function (VideoPlayer) {
* methods, modules) of the Video player. * methods, modules) of the Video player.
*/ */
function _makeFunctionsPublic(state) { function _makeFunctionsPublic(state) {
state.setSpeed = _.bind(setSpeed, state); state.setSpeed = _.bind(setSpeed, state);
state.youtubeId = _.bind(youtubeId, state); state.youtubeId = _.bind(youtubeId, state);
state.getDuration = _.bind(getDuration, state); state.getDuration = _.bind(getDuration, state);
state.trigger = _.bind(trigger, state); state.trigger = _.bind(trigger, state);
state.stopBuffering = _.bind(stopBuffering, state); state.stopBuffering = _.bind(stopBuffering, state);
// Old private functions. Now also public so that can be // Old private functions. Now also public so that can be
// tested by Jasmine. // tested by Jasmine.
state.initialize = _.bind(initialize, state); state.initialize = _.bind(initialize, state);
state.parseSpeed = _.bind(parseSpeed, state); state.parseSpeed = _.bind(parseSpeed, state);
state.fetchMetadata = _.bind(fetchMetadata, state); state.fetchMetadata = _.bind(fetchMetadata, state);
state.parseYoutubeStreams = _.bind(parseYoutubeStreams, state); state.parseYoutubeStreams = _.bind(parseYoutubeStreams, state);
state.parseVideoSources = _.bind(parseVideoSources, state); state.parseVideoSources = _.bind(parseVideoSources, state);
state.getVideoMetadata = _.bind(getVideoMetadata, state); state.getVideoMetadata = _.bind(getVideoMetadata, state);
} }
// function _renderElements(state) // function _renderElements(state)
// //
// Create any necessary DOM elements, attach them, and set their initial configuration. Also // Create any necessary DOM elements, attach them, and set their
// make the created DOM elements available via the 'state' object. Much easier to work this // initial configuration. Also make the created DOM elements available
// way - you don't have to do repeated jQuery element selects. // via the 'state' object. Much easier to work this way - you don't
// have to do repeated jQuery element selects.
function _renderElements(state) { function _renderElements(state) {
// Launch embedding of actual video content, or set it up so that it will be done as soon as the // Launch embedding of actual video content, or set it up so that it
// appropriate video player (YouTube or stand alone HTML5) is loaded, and can handle embedding. // will be done as soon as the appropriate video player (YouTube or
// stand-alone HTML5) is loaded, and can handle embedding.
// //
// Note that the loading of stand alone HTML5 player API is handled by Require JS. At the time // Note that the loading of stand alone HTML5 player API is handled by
// when we reach this code, the stand alone HTML5 player is already loaded, so no further testing // Require JS. At the time when we reach this code, the stand alone
// in that case is required. // HTML5 player is already loaded, so no further testing in that case
// is required.
var onPlayerReadyFunc; var onPlayerReadyFunc;
if ( if (
((state.videoType === 'youtube') && (window.YT) && (window.YT.Player)) || (
(state.videoType === 'youtube') &&
(window.YT) &&
(window.YT.Player)
) ||
(state.videoType === 'html5') (state.videoType === 'html5')
) { ) {
VideoPlayer(state); VideoPlayer(state);
} else { } else {
onPlayerReadyFunc = (state.videoType === 'youtube') ? 'onYouTubePlayerAPIReady' : 'onHTML5PlayerAPIReady'; if (state.videoType === 'youtube') {
onPlayerReadyFunc = 'onYouTubePlayerAPIReady';
} else {
onPlayerReadyFunc = 'onHTML5PlayerAPIReady';
}
window[onPlayerReadyFunc] = _.bind(VideoPlayer, window, state); window[onPlayerReadyFunc] = _.bind(VideoPlayer, window, state);
} }
} }
...@@ -121,7 +123,8 @@ function (VideoPlayer) { ...@@ -121,7 +123,8 @@ function (VideoPlayer) {
// Option // Option
// this.hide_captions = true | false // this.hide_captions = true | false
// //
// represents the user's choice of having the subtitles shown or hidden. This choice is stored in cookies. // represents the user's choice of having the subtitles shown or
// hidden. This choice is stored in cookies.
function _configureCaptions(state) { function _configureCaptions(state) {
if (state.config.show_captions) { if (state.config.show_captions) {
state.hide_captions = ($.cookie('hide_captions') === 'true'); state.hide_captions = ($.cookie('hide_captions') === 'true');
...@@ -138,14 +141,19 @@ function (VideoPlayer) { ...@@ -138,14 +141,19 @@ function (VideoPlayer) {
} }
// function _setPlayerMode(state) // function _setPlayerMode(state)
// By default we will be forcing HTML5 player mode. Only in the case when, after initializtion, we will // By default we will be forcing HTML5 player mode. Only in the case
// get one available playback rate, we will change to Flash player mode. There is a need to store this // when, after initializtion, we will get one available playback rate,
// setting in cookies because otherwise we will have to change from HTML5 to Flash on every page load // we will change to Flash player mode. There is a need to store this
// in a browser that doesn't fully support HTML5. When we have this setting in cookies, we can select // setting in cookies because otherwise we will have to change from
// HTML5 to Flash on every page load in a browser that doesn't fully
// support HTML5. When we have this setting in cookies, we can select
// the proper mode from the start (not having to change mode later on). // the proper mode from the start (not having to change mode later on).
function _setPlayerMode(state) { function _setPlayerMode(state) {
(function (currentPlayerMode) { (function (currentPlayerMode) {
if ((currentPlayerMode === 'html5') || (currentPlayerMode === 'flash')) { if (
(currentPlayerMode === 'html5') ||
(currentPlayerMode === 'flash')
) {
state.currentPlayerMode = currentPlayerMode; state.currentPlayerMode = currentPlayerMode;
} else { } else {
$.cookie('current_player_mode', 'html5', { $.cookie('current_player_mode', 'html5', {
...@@ -154,20 +162,32 @@ function (VideoPlayer) { ...@@ -154,20 +162,32 @@ function (VideoPlayer) {
}); });
state.currentPlayerMode = 'html5'; state.currentPlayerMode = 'html5';
} }
console.log(
'[Video info]: YouTube player mode is "' +
state.currentPlayerMode + '".'
);
}($.cookie('current_player_mode'))); }($.cookie('current_player_mode')));
} }
// function _parseYouTubeIDs(state) // function _parseYouTubeIDs(state)
// The function parse YouTube stream ID's. // The function parse YouTube stream ID's.
// @return // @return
// false: We don't have YouTube video IDs to work with; most likely we have HTML5 video sources. // false: We don't have YouTube video IDs to work with; most likely
// true: Parsing of YouTube video IDs went OK, and we can proceed onwards to play YouTube videos. // we have HTML5 video sources.
// true: Parsing of YouTube video IDs went OK, and we can proceed
// onwards to play YouTube videos.
function _parseYouTubeIDs(state) { function _parseYouTubeIDs(state) {
if (state.parseYoutubeStreams(state.config.youtubeStreams)) { if (state.parseYoutubeStreams(state.config.youtubeStreams)) {
state.videoType = 'youtube'; state.videoType = 'youtube';
return true; return true;
} }
console.log(
'[Video info]: Youtube Video IDs are incorrect or absent.'
);
return false; return false;
} }
...@@ -203,6 +223,10 @@ function (VideoPlayer) { ...@@ -203,6 +223,10 @@ function (VideoPlayer) {
state.el.find('.video-player div').addClass('hidden'); state.el.find('.video-player div').addClass('hidden');
state.el.find('.video-player h3').removeClass('hidden'); state.el.find('.video-player h3').removeClass('hidden');
console.log(
'[Video info]: Non-youtube video sources aren\'t available.'
);
return false; return false;
} }
...@@ -231,8 +255,9 @@ function (VideoPlayer) { ...@@ -231,8 +255,9 @@ function (VideoPlayer) {
// *************************************************************** // ***************************************************************
// Public functions start here. // Public functions start here.
// These are available via the 'state' object. Their context ('this' keyword) is the 'state' object. // These are available via the 'state' object. Their context ('this'
// The magic private function that makes them available and sets up their context is makeFunctionsPublic(). // keyword) is the 'state' object. The magic private function that makes
// them available and sets up their context is makeFunctionsPublic().
// *************************************************************** // ***************************************************************
// function initialize(element) // function initialize(element)
...@@ -240,17 +265,21 @@ function (VideoPlayer) { ...@@ -240,17 +265,21 @@ function (VideoPlayer) {
function initialize(element) { function initialize(element) {
var _this = this, tempYtTestTimeout; var _this = this, tempYtTestTimeout;
// This is used in places where we instead would have to check if an element has a CSS class 'fullscreen'. // This is used in places where we instead would have to check if an
// element has a CSS class 'fullscreen'.
this.isFullScreen = false; this.isFullScreen = false;
// The parent element of the video, and the ID. // The parent element of the video, and the ID.
this.el = $(element).find('.video'); this.el = $(element).find('.video');
this.id = this.el.attr('id').replace(/video_/, ''); this.id = this.el.attr('id').replace(/video_/, '');
console.log('[Video info]: Initializing video with id "' + this.id + '".'); console.log(
'[Video info]: Initializing video with id "' + this.id + '".'
);
// We store all settings passed to us by the server in one place. These are "read only", so don't // We store all settings passed to us by the server in one place. These
// modify them. All variable content lives in 'state' object. // are "read only", so don't modify them. All variable content lives in
// 'state' object.
this.config = { this.config = {
element: element, element: element,
...@@ -259,7 +288,10 @@ function (VideoPlayer) { ...@@ -259,7 +288,10 @@ function (VideoPlayer) {
caption_data_dir: this.el.data('caption-data-dir'), caption_data_dir: this.el.data('caption-data-dir'),
caption_asset_path: this.el.data('caption-asset-path'), caption_asset_path: this.el.data('caption-asset-path'),
show_captions: (this.el.data('show-captions').toString().toLowerCase() === 'true'), show_captions: (
this.el.data('show-captions')
.toString().toLowerCase() === 'true'
),
youtubeStreams: this.el.data('streams'), youtubeStreams: this.el.data('streams'),
sub: this.el.data('sub'), sub: this.el.data('sub'),
...@@ -282,62 +314,70 @@ function (VideoPlayer) { ...@@ -282,62 +314,70 @@ function (VideoPlayer) {
} }
this.config.ytTestTimeout = tempYtTestTimeout; this.config.ytTestTimeout = tempYtTestTimeout;
console.log('[Video info]: Try parsing YouTube IDs.');
if (!(_parseYouTubeIDs(this))) { if (!(_parseYouTubeIDs(this))) {
console.log('[Video info]: Could not parse YouTube IDs. Try parsing non-YouTube video sources.');
// If we do not have YouTube ID's, try parsing HTML5 video sources. // If we do not have YouTube ID's, try parsing HTML5 video sources.
if (!_prepareHTML5Video(this, true)) { if (!_prepareHTML5Video(this, true)) {
console.log('[Video info]: No video sources available at all.');
// Non-YouTube sources were not found either. // Non-YouTube sources were not found either.
return; return;
} }
console.log('[Video info]: Start player in HTML5 mode.');
_setConfigurations(this); _setConfigurations(this);
_renderElements(this); _renderElements(this);
} else { } else {
console.log('[Video info]: Valid YouTube IDs found.');
if (!this.youtubeXhr) { if (!this.youtubeXhr) {
console.log('[Video info]: Making request to see if YouTube responds.');
this.youtubeXhr = this.getVideoMetadata(); this.youtubeXhr = this.getVideoMetadata();
} }
console.log('[Video info]: Waiting for YouTube to respond.');
this.youtubeXhr this.youtubeXhr
.always(function(json, status) { .always(function (json, status) {
var err = $.isPlainObject(json.error) || var err = $.isPlainObject(json.error) ||
(status !== 'success' && status !== 'notmodified'); (
status !== 'success' &&
status !== 'notmodified'
);
if (err) { if (err) {
console.log('[Video info]: An error happened while waiting for YouTube to respond.'); console.log(
console.log('[Video info]: Checking if alternate non-YouTube video sources are available.'); '[Video info]: YouTube returned an error for ' +
'video with id "' + _this.id + '".'
);
// When the youtube link doesn't work for any reason // When the youtube link doesn't work for any reason
// (for example, the great firewall in china) any // (for example, the great firewall in china) any
// alternate sources should automatically play. // alternate sources should automatically play.
if (!_prepareHTML5Video(_this)) { if (!_prepareHTML5Video(_this)) {
console.log('[Video info]: No alternative non-YouTube video sources available.'); console.log(
console.log('[Video info]: Will try to continue loading YouTube video. Maybe the response timeout is longer than was specified.'); '[Video info]: Continue loading ' +
'YouTube video.'
);
// Non-YouTube sources were not found either. // Non-YouTube sources were not found either.
_this.el.find('.video-player div').removeClass('hidden'); _this.el.find('.video-player div')
_this.el.find('.video-player h3').addClass('hidden'); .removeClass('hidden');
_this.el.find('.video-player h3')
.addClass('hidden');
// If in reality the timeout was to short, try to // If in reality the timeout was to short, try to
// continue loading the YouTube video anyways. // continue loading the YouTube video anyways.
_this.fetchMetadata(); _this.fetchMetadata();
_this.parseSpeed(); _this.parseSpeed();
} else { } else {
console.log('[Video info]: Alternative non-YouTube video sources were found and will be loaded.'); console.log(
'[Video info]: Change player mode to HTML5.'
);
// In-browser HTML5 player does not support quality // In-browser HTML5 player does not support quality
// control. // control.
_this.el.find('a.quality_control').hide(); _this.el.find('a.quality_control').hide();
} }
} else { } else {
console.log('[Video info]: YouTube responded with OK. Loading YouTube video.'); console.log(
'[Video info]: Start player in YouTube mode.'
);
_this.fetchMetadata(); _this.fetchMetadata();
_this.parseSpeed(); _this.parseSpeed();
...@@ -353,23 +393,28 @@ function (VideoPlayer) { ...@@ -353,23 +393,28 @@ function (VideoPlayer) {
// //
// Take a string in the form: // Take a string in the form:
// "iCawTYPtehk:0.75,KgpclqP-LBA:1.0,9-2670d5nvU:1.5" // "iCawTYPtehk:0.75,KgpclqP-LBA:1.0,9-2670d5nvU:1.5"
// parse it, and make it available via the 'state' object. If we are not given a string, or // parse it, and make it available via the 'state' object. If we are
// it's length is zero, then we return false. // not given a string, or it's length is zero, then we return false.
// //
// @return // @return
// false: We don't have YouTube video IDs to work with; most likely we have HTML5 video sources. // false: We don't have YouTube video IDs to work with; most likely
// true: Parsing of YouTube video IDs went OK, and we can proceed onwards to play YouTube videos. // we have HTML5 video sources.
// true: Parsing of YouTube video IDs went OK, and we can proceed
// onwards to play YouTube videos.
function parseYoutubeStreams(youtubeStreams) { function parseYoutubeStreams(youtubeStreams) {
var _this; var _this;
if (typeof youtubeStreams === 'undefined' || youtubeStreams.length === 0) { if (
typeof youtubeStreams === 'undefined' ||
youtubeStreams.length === 0
) {
return false; return false;
} }
_this = this; _this = this;
this.videos = {}; this.videos = {};
$.each(youtubeStreams.split(/,/), function(index, video) { $.each(youtubeStreams.split(/,/), function (index, video) {
var speed; var speed;
video = video.split(/:/); video = video.split(/:/);
...@@ -383,8 +428,8 @@ function (VideoPlayer) { ...@@ -383,8 +428,8 @@ function (VideoPlayer) {
// function parseVideoSources(, mp4Source, webmSource, oggSource) // function parseVideoSources(, mp4Source, webmSource, oggSource)
// //
// Take the HTML5 sources (URLs of videos), and make them available explictly for each type // Take the HTML5 sources (URLs of videos), and make them available
// of video format (mp4, webm, ogg). // explictly for each type of video format (mp4, webm, ogg).
function parseVideoSources(sources) { function parseVideoSources(sources) {
var _this = this, var _this = this,
v = document.createElement('video'), v = document.createElement('video'),
...@@ -416,16 +461,17 @@ function (VideoPlayer) { ...@@ -416,16 +461,17 @@ function (VideoPlayer) {
// function fetchMetadata() // function fetchMetadata()
// //
// When dealing with YouTube videos, we must fetch meta data that has certain key facts // When dealing with YouTube videos, we must fetch meta data that has
// not available while the video is loading. For example the length of the video can be // certain key facts not available while the video is loading. For
// determined from the meta data. // example the length of the video can be determined from the meta
// data.
function fetchMetadata() { function fetchMetadata() {
var _this = this; var _this = this;
this.metadata = {}; this.metadata = {};
$.each(this.videos, function (speed, url) { $.each(this.videos, function (speed, url) {
_this.getVideoMetadata(url, function(data) { _this.getVideoMetadata(url, function (data) {
if (data.data) { if (data.data) {
_this.metadata[data.data.id] = data.data; _this.metadata[data.data.id] = data.data;
} }
...@@ -437,7 +483,7 @@ function (VideoPlayer) { ...@@ -437,7 +483,7 @@ function (VideoPlayer) {
// //
// Create a separate array of available speeds. // Create a separate array of available speeds.
function parseSpeed() { function parseSpeed() {
this.speeds = ($.map(this.videos, function(url, speed) { this.speeds = ($.map(this.videos, function (url, speed) {
return speed; return speed;
})).sort(); })).sort();
...@@ -479,7 +525,7 @@ function (VideoPlayer) { ...@@ -479,7 +525,7 @@ function (VideoPlayer) {
function stopBuffering() { function stopBuffering() {
var video; var video;
if (this.videoType === 'html5'){ if (this.videoType === 'html5') {
// HTML5 player haven't default way to abort bufferization. // HTML5 player haven't default way to abort bufferization.
// In this case we simply resetting source and call load(). // In this case we simply resetting source and call load().
video = this.videoPlayer.player.video; video = this.videoPlayer.player.video;
...@@ -497,9 +543,9 @@ function (VideoPlayer) { ...@@ -497,9 +543,9 @@ function (VideoPlayer) {
} }
/* /*
* The trigger() function will assume that the @objChain is a complete chain with a method * The trigger() function will assume that the @objChain is a complete
* (function) at the end. It will call this function. So for example, when trigger() is * chain with a method (function) at the end. It will call this function.
* called like so: * So for example, when trigger() is called like so:
* *
* state.trigger('videoPlayer.pause', {'param1': 10}); * state.trigger('videoPlayer.pause', {'param1': 10});
* *
...@@ -514,10 +560,10 @@ function (VideoPlayer) { ...@@ -514,10 +560,10 @@ function (VideoPlayer) {
tmpObj = this; tmpObj = this;
chain = objChain.split('.'); chain = objChain.split('.');
// At the end of the loop the variable 'tmpObj' will either be the correct // At the end of the loop the variable 'tmpObj' will either be the
// object/function to trigger/invoke. If the 'chain' chain of object is // correct object/function to trigger/invoke. If the 'chain' chain of
// incorrect (one of the link is non-existent), then the loop will immediately // object is incorrect (one of the link is non-existent), then the loop
// exit. // will immediately exit.
while (chain.length) { while (chain.length) {
i = chain.shift(); i = chain.shift();
......
...@@ -21,35 +21,46 @@ function (HTML5Video) { ...@@ -21,35 +21,46 @@ function (HTML5Video) {
// function _makeFunctionsPublic(state) // function _makeFunctionsPublic(state)
// //
// Functions which will be accessible via 'state' object. When called, these functions will // Functions which will be accessible via 'state' object. When called,
// get the 'state' object as a context. // these functions will get the 'state' object as a context.
function _makeFunctionsPublic(state) { function _makeFunctionsPublic(state) {
state.videoPlayer.pause = _.bind(pause, state); state.videoPlayer.pause = _.bind(pause, state);
state.videoPlayer.play = _.bind(play, state); state.videoPlayer.play = _.bind(play, state);
state.videoPlayer.update = _.bind(update, state); state.videoPlayer.update = _.bind(update, state);
state.videoPlayer.onSpeedChange = _.bind(onSpeedChange, state); state.videoPlayer.onSpeedChange = _.bind(onSpeedChange, state);
state.videoPlayer.onCaptionSeek = _.bind(onSeek, state); state.videoPlayer.onCaptionSeek = _.bind(onSeek, state);
state.videoPlayer.onSlideSeek = _.bind(onSeek, state); state.videoPlayer.onSlideSeek = _.bind(onSeek, state);
state.videoPlayer.onEnded = _.bind(onEnded, state); state.videoPlayer.onEnded = _.bind(onEnded, state);
state.videoPlayer.onPause = _.bind(onPause, state); state.videoPlayer.onPause = _.bind(onPause, state);
state.videoPlayer.onPlay = _.bind(onPlay, state); state.videoPlayer.onPlay = _.bind(onPlay, state);
state.videoPlayer.onUnstarted = _.bind(onUnstarted, state);
state.videoPlayer.handlePlaybackQualityChange = _.bind(handlePlaybackQualityChange, state); state.videoPlayer.onUnstarted = _.bind(
state.videoPlayer.onPlaybackQualityChange = _.bind(onPlaybackQualityChange, state); onUnstarted, state
state.videoPlayer.onStateChange = _.bind(onStateChange, state); );
state.videoPlayer.onReady = _.bind(onReady, state);
state.videoPlayer.updatePlayTime = _.bind(updatePlayTime, state); state.videoPlayer.handlePlaybackQualityChange = _.bind(
state.videoPlayer.isPlaying = _.bind(isPlaying, state); handlePlaybackQualityChange, state
state.videoPlayer.log = _.bind(log, state); );
state.videoPlayer.duration = _.bind(duration, state);
state.videoPlayer.onVolumeChange = _.bind(onVolumeChange, state); state.videoPlayer.onPlaybackQualityChange = _.bind(
onPlaybackQualityChange, state
);
state.videoPlayer.onStateChange = _.bind(onStateChange, state);
state.videoPlayer.onReady = _.bind(onReady, state);
state.videoPlayer.updatePlayTime = _.bind(updatePlayTime, state);
state.videoPlayer.isPlaying = _.bind(isPlaying, state);
state.videoPlayer.log = _.bind(log, state);
state.videoPlayer.duration = _.bind(duration, state);
state.videoPlayer.onVolumeChange = _.bind(onVolumeChange, state);
} }
// function _initialize(state) // function _initialize(state)
// //
// Create any necessary DOM elements, attach them, and set their initial configuration. Also // Create any necessary DOM elements, attach them, and set their
// make the created DOM elements available via the 'state' object. Much easier to work this // initial configuration. Also make the created DOM elements available
// way - you don't have to do repeated jQuery element selects. // via the 'state' object. Much easier to work this way - you don't
// have to do repeated jQuery element selects.
function _initialize(state) { function _initialize(state) {
var youTubeId; var youTubeId;
...@@ -92,9 +103,10 @@ function (HTML5Video) { ...@@ -92,9 +103,10 @@ function (HTML5Video) {
// //
// TODO: Check the status of // TODO: Check the status of
// http://code.google.com/p/gdata-issues/issues/detail?id=4654 // http://code.google.com/p/gdata-issues/issues/detail?id=4654
// When the YouTube team fixes the API bug, we can remove this temporary // When the YouTube team fixes the API bug, we can remove this
// bug fix. // temporary bug fix.
state.browserIsFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1; state.browserIsFirefox = navigator.userAgent
.toLowerCase().indexOf('firefox') > -1;
if (state.videoType === 'html5') { if (state.videoType === 'html5') {
state.videoPlayer.player = new HTML5Video.Player(state.el, { state.videoPlayer.player = new HTML5Video.Player(state.el, {
...@@ -117,7 +129,8 @@ function (HTML5Video) { ...@@ -117,7 +129,8 @@ function (HTML5Video) {
events: { events: {
onReady: state.videoPlayer.onReady, onReady: state.videoPlayer.onReady,
onStateChange: state.videoPlayer.onStateChange, onStateChange: state.videoPlayer.onStateChange,
onPlaybackQualityChange: state.videoPlayer.onPlaybackQualityChange onPlaybackQualityChange: state.videoPlayer
.onPlaybackQualityChange
} }
}); });
} }
...@@ -125,9 +138,10 @@ function (HTML5Video) { ...@@ -125,9 +138,10 @@ function (HTML5Video) {
// function _restartUsingFlash(state) // function _restartUsingFlash(state)
// //
// When we are about to play a YouTube video in HTML5 mode and discover that we only // When we are about to play a YouTube video in HTML5 mode and discover
// have one available playback rate, we will switch to Flash mode. In Flash speed // that we only have one available playback rate, we will switch to
// switching is done by reloading videos recorded at different frame rates. // Flash mode. In Flash speed switching is done by reloading videos
// recorded at different frame rates.
function _restartUsingFlash(state) { function _restartUsingFlash(state) {
// Remove from the page current iFrame with HTML5 video. // Remove from the page current iFrame with HTML5 video.
state.videoPlayer.player.destroy(); state.videoPlayer.player.destroy();
...@@ -139,25 +153,29 @@ function (HTML5Video) { ...@@ -139,25 +153,29 @@ function (HTML5Video) {
}); });
state.currentPlayerMode = 'flash'; state.currentPlayerMode = 'flash';
console.log('[Video info]: Changing YouTube player mode to "flash".');
// Removed configuration option that requests the HTML5 mode. // Removed configuration option that requests the HTML5 mode.
delete state.videoPlayer.playerVars.html5; delete state.videoPlayer.playerVars.html5;
// Request for the creation of a new Flash player // Request for the creation of a new Flash player
state.videoPlayer.player = new YT.Player(state.id, { state.videoPlayer.player = new YT.Player(state.id, {
'playerVars': state.videoPlayer.playerVars, playerVars: state.videoPlayer.playerVars,
'videoId': state.youtubeId(), videoId: state.youtubeId(),
'events': { events: {
'onReady': state.videoPlayer.onReady, onReady: state.videoPlayer.onReady,
'onStateChange': state.videoPlayer.onStateChange, onStateChange: state.videoPlayer.onStateChange,
'onPlaybackQualityChange': state.videoPlayer.onPlaybackQualityChange onPlaybackQualityChange: state.videoPlayer
.onPlaybackQualityChange
} }
}); });
} }
// *************************************************************** // ***************************************************************
// Public functions start here. // Public functions start here.
// These are available via the 'state' object. Their context ('this' keyword) is the 'state' object. // These are available via the 'state' object. Their context ('this'
// The magic private function that makes them available and sets up their context is makeFunctionsPublic(). // keyword) is the 'state' object. The magic private function that makes
// them available and sets up their context is makeFunctionsPublic().
// *************************************************************** // ***************************************************************
function pause() { function pause() {
...@@ -174,9 +192,11 @@ function (HTML5Video) { ...@@ -174,9 +192,11 @@ function (HTML5Video) {
// This function gets the video's current play position in time // This function gets the video's current play position in time
// (currentTime) and its duration. // (currentTime) and its duration.
// It is called at a regular interval when the video is playing (see below). // It is called at a regular interval when the video is playing (see
// below).
function update() { function update() {
this.videoPlayer.currentTime = this.videoPlayer.player.getCurrentTime(); this.videoPlayer.currentTime = this.videoPlayer.player
.getCurrentTime();
if (isFinite(this.videoPlayer.currentTime)) { if (isFinite(this.videoPlayer.currentTime)) {
this.videoPlayer.updatePlayTime(this.videoPlayer.currentTime); this.videoPlayer.updatePlayTime(this.videoPlayer.currentTime);
...@@ -206,19 +226,27 @@ function (HTML5Video) { ...@@ -206,19 +226,27 @@ function (HTML5Video) {
if ( if (
this.currentPlayerMode === 'html5' && this.currentPlayerMode === 'html5' &&
!(this.browserIsFirefox && newSpeed === '1.0' && this.videoType === 'youtube') !(
this.browserIsFirefox &&
newSpeed === '1.0' &&
this.videoType === 'youtube'
)
) { ) {
this.videoPlayer.player.setPlaybackRate(newSpeed); this.videoPlayer.player.setPlaybackRate(newSpeed);
} else { } else {
// We request the reloading of the video in the case when YouTube is in // We request the reloading of the video in the case when YouTube
// Flash player mode, or when we are in Firefox, and the new speed is 1.0. // is in Flash player mode, or when we are in Firefox, and the new
// The second case is necessary to avoid the bug where in Firefox speed // speed is 1.0. The second case is necessary to avoid the bug
// switching to 1.0 in HTML5 player mode is handled incorrectly by YouTube // where in Firefox speed switching to 1.0 in HTML5 player mode is
// API. // handled incorrectly by YouTube API.
if (this.videoPlayer.isPlaying()) { if (this.videoPlayer.isPlaying()) {
this.videoPlayer.player.loadVideoById(this.youtubeId(), this.videoPlayer.currentTime); this.videoPlayer.player.loadVideoById(
this.youtubeId(), this.videoPlayer.currentTime
);
} else { } else {
this.videoPlayer.player.cueVideoById(this.youtubeId(), this.videoPlayer.currentTime); this.videoPlayer.player.cueVideoById(
this.youtubeId(), this.videoPlayer.currentTime
);
} }
this.videoPlayer.updatePlayTime(this.videoPlayer.currentTime); this.videoPlayer.updatePlayTime(this.videoPlayer.currentTime);
...@@ -243,7 +271,9 @@ function (HTML5Video) { ...@@ -243,7 +271,9 @@ function (HTML5Video) {
if (this.videoPlayer.isPlaying()) { if (this.videoPlayer.isPlaying()) {
clearInterval(this.videoPlayer.updateInterval); clearInterval(this.videoPlayer.updateInterval);
this.videoPlayer.updateInterval = setInterval(this.videoPlayer.update, 200); this.videoPlayer.updateInterval = setInterval(
this.videoPlayer.update, 200
);
} else { } else {
this.videoPlayer.currentTime = params.time; this.videoPlayer.currentTime = params.time;
} }
...@@ -286,7 +316,9 @@ function (HTML5Video) { ...@@ -286,7 +316,9 @@ function (HTML5Video) {
); );
if (!this.videoPlayer.updateInterval) { if (!this.videoPlayer.updateInterval) {
this.videoPlayer.updateInterval = setInterval(this.videoPlayer.update, 200); this.videoPlayer.updateInterval = setInterval(
this.videoPlayer.update, 200
);
} }
this.trigger('videoControl.play', null); this.trigger('videoControl.play', null);
...@@ -325,19 +357,26 @@ function (HTML5Video) { ...@@ -325,19 +357,26 @@ function (HTML5Video) {
// https://bugzilla.mozilla.org/show_bug.cgi?id=840745 // https://bugzilla.mozilla.org/show_bug.cgi?id=840745
// https://developer.mozilla.org/en-US/docs/DOM/HTMLMediaElement // https://developer.mozilla.org/en-US/docs/DOM/HTMLMediaElement
availablePlaybackRates = _.filter(availablePlaybackRates, function(item){ availablePlaybackRates = _.filter(
var speed = Number(item); availablePlaybackRates,
return speed > 0.25 && speed <= 5; function (item) {
}); var speed = Number(item);
return speed > 0.25 && speed <= 5;
}
);
if ((this.currentPlayerMode === 'html5') && (this.videoType === 'youtube')) { if (
this.currentPlayerMode === 'html5' &&
this.videoType === 'youtube'
) {
if (availablePlaybackRates.length === 1) { if (availablePlaybackRates.length === 1) {
// This condition is needed in cases when Firefox version is less than 20. In those versions // This condition is needed in cases when Firefox version is
// HTML5 playback could only happen at 1 speed (no speed changing). Therefore, in this case, // less than 20. In those versions HTML5 playback could only
// we need to switch back to Flash. // happen at 1 speed (no speed changing). Therefore, in this
// case, we need to switch back to Flash.
// //
// This might also happen in other browsers, therefore when we have 1 speed available, we fall // This might also happen in other browsers, therefore when we
// back to Flash. // have 1 speed available, we fall back to Flash.
_restartUsingFlash(this); _restartUsingFlash(this);
...@@ -352,18 +391,26 @@ function (HTML5Video) { ...@@ -352,18 +391,26 @@ function (HTML5Video) {
// and their associated subs. // and their associated subs.
// First clear the dictionary. // First clear the dictionary.
$.each(this.videos, function(index, value) { $.each(this.videos, function (index, value) {
delete _this.videos[index]; delete _this.videos[index];
}); });
this.speeds = []; this.speeds = [];
// Recreate it with the supplied frame rates. // Recreate it with the supplied frame rates.
$.each(availablePlaybackRates, function(index, value) { $.each(availablePlaybackRates, function (index, value) {
_this.videos[value.toFixed(2).replace(/\.00$/, '.0')] = baseSpeedSubs; var key = value.toFixed(2).replace(/\.00$/, '.0');
_this.videos[key] = baseSpeedSubs;
_this.speeds.push(value.toFixed(2).replace(/\.00$/, '.0')); _this.speeds.push(key);
}); });
this.trigger('videoSpeedControl.reRender', {'newSpeeds': this.speeds, 'currentSpeed': this.speed}); this.trigger(
'videoSpeedControl.reRender',
{
newSpeeds: this.speeds,
currentSpeed: this.speed
}
);
this.setSpeed($.cookie('video_speed')); this.setSpeed($.cookie('video_speed'));
} }
...@@ -373,7 +420,10 @@ function (HTML5Video) { ...@@ -373,7 +420,10 @@ function (HTML5Video) {
this.videoPlayer.player.setPlaybackRate(this.speed); this.videoPlayer.player.setPlaybackRate(this.speed);
} }
if (!onTouchBasedDevice() && $('.video:first').data('autoplay') === 'True') { if (
!onTouchBasedDevice() &&
$('.video:first').data('autoplay') === 'True'
) {
this.videoPlayer.play(); this.videoPlayer.play();
} }
} }
...@@ -400,19 +450,37 @@ function (HTML5Video) { ...@@ -400,19 +450,37 @@ function (HTML5Video) {
duration = this.videoPlayer.duration(); duration = this.videoPlayer.duration();
this.trigger('videoProgressSlider.updatePlayTime', {'time': time, 'duration': duration}); this.trigger(
this.trigger('videoControl.updateVcrVidTime', {'time': time, 'duration': duration}); 'videoProgressSlider.updatePlayTime',
{
time: time,
duration: duration
}
);
this.trigger(
'videoControl.updateVcrVidTime',
{
time: time,
duration: duration
}
);
this.trigger('videoCaption.updatePlayTime', time); this.trigger('videoCaption.updatePlayTime', time);
} }
function isPlaying() { function isPlaying() {
return this.videoPlayer.player.getPlayerState() === this.videoPlayer.PlayerState.PLAYING; var playerState = this.videoPlayer.player.getPlayerState(),
PLAYING = this.videoPlayer.PlayerState.PLAYING;
return playerState === PLAYING;
} }
function duration() { function duration() {
var dur; var dur;
dur = this.videoPlayer.player.getDuration(); dur = this.videoPlayer.player.getDuration();
if (!isFinite(dur)) { if (!isFinite(dur)) {
dur = this.getDuration(); dur = this.getDuration();
} }
...@@ -431,7 +499,7 @@ function (HTML5Video) { ...@@ -431,7 +499,7 @@ function (HTML5Video) {
// If extra parameters were passed to the log. // If extra parameters were passed to the log.
if (data) { if (data) {
$.each(data, function(paramName, value) { $.each(data, function (paramName, value) {
logInfo[paramName] = value; logInfo[paramName] = value;
}); });
} }
......
...@@ -19,6 +19,10 @@ function () { ...@@ -19,6 +19,10 @@ function () {
} }
if (state.videoType === 'html5' && !(_checkPlaybackRates())) { if (state.videoType === 'html5' && !(_checkPlaybackRates())) {
console.log(
'[Video info]: HTML5 mode - playbackRate is not supported.'
);
_hideSpeedControl(state); _hideSpeedControl(state);
return; return;
...@@ -65,7 +69,7 @@ function () { ...@@ -65,7 +69,7 @@ function () {
state.videoSpeedControl.el state.videoSpeedControl.el
); );
$.each(state.videoSpeedControl.speeds, function(index, speed) { $.each(state.videoSpeedControl.speeds, function (index, speed) {
var link = '<a class="speed_link" href="#">' + speed + 'x</a>'; var link = '<a class="speed_link" href="#">' + speed + 'x</a>';
state.videoSpeedControl.videoSpeedsEl state.videoSpeedControl.videoSpeedsEl
...@@ -78,26 +82,27 @@ function () { ...@@ -78,26 +82,27 @@ function () {
} }
/** /**
* @desc Check if playbackRate supports by browser. * @desc Check if playbackRate supports by browser.
* *
* @type {function} * @type {function}
* @access private * @access private
* *
* @param {object} state The object containg the state of the video player. * @param {object} state The object containg the state of the video player.
* All other modules, their parameters, public variables, etc. are * All other modules, their parameters, public variables, etc. are
* available via this object. * available via this object.
* *
* @this {object} The global window object. * @this {object} The global window object.
* *
* @returns {Boolean} * @returns {Boolean}
* true: Browser support playbackRate functionality. * true: Browser support playbackRate functionality.
* false: Browser doesn't support playbackRate functionality. * false: Browser doesn't support playbackRate functionality.
*/ */
function _checkPlaybackRates() { function _checkPlaybackRates() {
var video = document.createElement('video'); var video = document.createElement('video');
// If browser supports, 1.0 should be returned by playbackRate property. // If browser supports, 1.0 should be returned by playbackRate
// In this case, function return True. Otherwise, False will be returned. // property. In this case, function return True. Otherwise, False will
// be returned.
return Boolean(video.playbackRate); return Boolean(video.playbackRate);
} }
...@@ -128,7 +133,7 @@ function () { ...@@ -128,7 +133,7 @@ function () {
.on('click', state.videoSpeedControl.changeVideoSpeed); .on('click', state.videoSpeedControl.changeVideoSpeed);
if (onTouchBasedDevice()) { if (onTouchBasedDevice()) {
state.videoSpeedControl.el.on('click', function(event) { state.videoSpeedControl.el.on('click', function (event) {
// So that you can't highlight this control via a drag // So that you can't highlight this control via a drag
// operation, we disable the default browser actions on a // operation, we disable the default browser actions on a
// click event. // click event.
...@@ -287,7 +292,7 @@ function () { ...@@ -287,7 +292,7 @@ function () {
this.videoSpeedControl.videoSpeedsEl.find('li').removeClass('active'); this.videoSpeedControl.videoSpeedsEl.find('li').removeClass('active');
this.videoSpeedControl.speeds = params.newSpeeds; this.videoSpeedControl.speeds = params.newSpeeds;
$.each(this.videoSpeedControl.speeds, function(index, speed) { $.each(this.videoSpeedControl.speeds, function (index, speed) {
var link, listItem; var link, listItem;
link = '<a class="speed_link" href="#">' + speed + 'x</a>'; link = '<a class="speed_link" href="#">' + speed + 'x</a>';
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment