Commit baf6bbe3 by Valera Rozuvan

Finalized HTML5 YouTube player inbrowser speed changes.

parent 143217d6
...@@ -25,7 +25,7 @@ class @VideoAlpha ...@@ -25,7 +25,7 @@ class @VideoAlpha
"1.0": sub "1.0": sub
"1.25": sub "1.25": sub
"1.5": sub "1.5": sub
@setSpeed($.cookie('video_speed')) @setSpeed $.cookie('video_speed')
$("#video_#{@id}").data('video', this).addClass('video-load-complete') $("#video_#{@id}").data('video', this).addClass('video-load-complete')
if @show_captions is true if @show_captions is true
@hide_captions = $.cookie('hide_captions') == 'true' @hide_captions = $.cookie('hide_captions') == 'true'
...@@ -47,7 +47,7 @@ class @VideoAlpha ...@@ -47,7 +47,7 @@ class @VideoAlpha
youtubeId: (speed)-> youtubeId: (speed)->
@videos[speed || @speed] @videos[speed || @speed]
VideoAlpha::parseVideos = (videos) -> parseVideos: (videos)->
return false if (typeof videos isnt "string") or (videos.length is 0) return false if (typeof videos isnt "string") or (videos.length is 0)
@videos = {} @videos = {}
_this = this _this = this
...@@ -58,7 +58,7 @@ class @VideoAlpha ...@@ -58,7 +58,7 @@ class @VideoAlpha
_this.videos[speed] = video[1] _this.videos[speed] = video[1]
true true
VideoAlpha::parseVideoSources = (mp4Source, webmSource, oggSource) -> parseVideoSources: (mp4Source, webmSource, oggSource)->
@html5Sources = @html5Sources =
mp4: null mp4: null
webm: null webm: null
...@@ -69,9 +69,9 @@ class @VideoAlpha ...@@ -69,9 +69,9 @@ class @VideoAlpha
parseSpeed: -> parseSpeed: ->
@speeds = ($.map @videos, (url, speed) -> speed).sort() @speeds = ($.map @videos, (url, speed) -> speed).sort()
@setSpeed($.cookie('video_speed')) @setSpeed $.cookie('video_speed')
VideoAlpha::setSpeed = (newSpeed) -> setSpeed: (newSpeed)->
if @speeds.indexOf(newSpeed) isnt -1 if @speeds.indexOf(newSpeed) isnt -1
@speed = newSpeed @speed = newSpeed
$.cookie "video_speed", "" + newSpeed, $.cookie "video_speed", "" + newSpeed,
...@@ -91,7 +91,7 @@ class @VideoAlpha ...@@ -91,7 +91,7 @@ class @VideoAlpha
getDuration: -> getDuration: ->
@metadata[@youtubeId()].duration @metadata[@youtubeId()].duration
VideoAlpha::log = (eventName) -> log: (eventName)->
logInfo = logInfo =
id: @id id: @id
code: @youtubeId() code: @youtubeId()
......
this.HTML5Video = (function () { this.HTML5Video = (function () {
var HTML5Video = {}; var HTML5Video;
HTML5Video = {};
HTML5Video.Player = (function () { HTML5Video.Player = (function () {
Player.prototype.callStateChangeCallback = function () {
if ($.isFunction(this.config.events.onStateChange) === true) {
this.config.events.onStateChange({
'data': this.playerState
});
}
};
Player.prototype.pauseVideo = function () {
this.video.pause();
};
Player.prototype.seekTo = function (value) {
if ((typeof value === 'number') && (value <= this.video.duration) && (value >= 0)) {
this.start = 0;
this.end = this.video.duration;
this.video.currentTime = value;
}
};
Player.prototype.setVolume = function (value) {
if ((typeof value === 'number') && (value <= 100) && (value >= 0)) {
this.video.volume = value * 0.01;
}
};
Player.prototype.getCurrentTime = function () {
return this.video.currentTime;
};
Player.prototype.playVideo = function () {
this.video.play();
};
Player.prototype.getPlayerState = function () {
return this.playerState;
};
Player.prototype.getVolume = function () {
return this.video.volume;
};
Player.prototype.getDuration = function () {
return this.video.duration;
};
Player.prototype.setPlaybackRate = function (value) {
var newSpeed;
newSpeed = parseFloat(value);
if (isFinite(newSpeed) === true) {
this.video.playbackRate = value;
}
};
Player.prototype.getAvailablePlaybackRates = function () {
return [0.75, 1.0, 1.25, 1.5];
};
return Player;
/* /*
* Constructor function for HTML5 Video player. * Constructor function for HTML5 Video player.
...@@ -16,17 +80,14 @@ this.HTML5Video = (function () { ...@@ -16,17 +80,14 @@ this.HTML5Video = (function () {
* *
* config = { * config = {
* *
* 'videoSources': {}, // An object of with properties being video sources. The property name is the * 'videoSources': {}, // An object with properties being video sources. The property name is the
* // video format of the source. Supported video formats are: 'mp4', 'webm', and * // video format of the source. Supported video formats are: 'mp4', 'webm', and
* // 'ogg'. By default videoSources property is null. This means that the * // 'ogg'.
* // player will initialize, and not play anything. If you do not provide a
* // 'videoSource' option, you can later call loadVideoBySource() method to load
* // a video and start playing it.
* *
* 'playerVars': { // Object's properties identify player parameters. * * 'playerVars': { // Object's properties identify player parameters.
* 'start': null, // Possible values: positive integer. Position from which to start playing the * 'start': 0, // Possible values: positive integer. Position from which to start playing the
* // video. Measured in seconds. If value is null, or 'start' property is not * // video. Measured in seconds. If value is non-numeric, or 'start' property is
* // specified, the video will start playing from the beginning. * // not specified, the video will start playing from the beginning.
* *
* 'end': null // Possible values: positive integer. Position when to stop playing the * 'end': null // Possible values: positive integer. Position when to stop playing the
* // video. Measured in seconds. If value is null, or 'end' property is not * // video. Measured in seconds. If value is null, or 'end' property is not
...@@ -47,51 +108,44 @@ this.HTML5Video = (function () { ...@@ -47,51 +108,44 @@ this.HTML5Video = (function () {
function Player(el, config) { function Player(el, config) {
var sourceStr, _this; var sourceStr, _this;
// If el is string, we assume it is an ID of a DOM element. Get the element, and check that the ID
// really belongs to an element. If we didn't get a DOM element, return. At this stage, nothing will
// break because other parts of the video player are waiting for 'onReady' callback to be called.
if (typeof el === 'string') { if (typeof el === 'string') {
this.el = $(el); this.el = $(el);
if (this.el.length === 0) {
return;
}
} else if (el instanceof jQuery) { } else if (el instanceof jQuery) {
this.el = el; this.el = el;
} else { } else {
// Error. Parameter el does not have a recognized type.
// TODO: Make sure that nothing breaks if one of the methods available via this object's prototype
// is called after we return.
return; return;
} }
// A simple test to see that the 'config' is a normal object.
if ($.isPlainObject(config) === true) { if ($.isPlainObject(config) === true) {
this.config = config; this.config = config;
} else { } else {
// Error. Parameter config does not have a recognized type.
// TODO: Make sure that nothing breaks if one of the methods available via this object's prototype
// is called after we return.
return; return;
} }
this.start = 0; // We should have at least one video source. Otherwise there is no point to continue.
this.end = null; if (config.hasOwnProperty('videoSources') === false) {
if (config.hasOwnProperty('playerVars') === true) { return;
this.start = parseFloat(config.playerVars.start);
if ((isFinite(this.start) !== true) || (this.start < 0)) {
this.start = 0;
}
this.end = parseFloat(config.playerVars.end);
if ((isFinite(this.end) !== true) || (this.end < this.start)) {
this.end = null;
}
} }
// From the start, all sources are empty. We will populate this object below.
sourceStr = { sourceStr = {
'mp4': ' ', 'mp4': ' ',
'webm': ' ', 'webm': ' ',
'ogg': ' ' 'ogg': ' '
}; };
// Will be used in inner functions to point to the current object.
_this = this; _this = this;
// Create HTML markup for individual sources of the HTML5 <video> element.
$.each(sourceStr, function (videoType, videoSource) { $.each(sourceStr, function (videoType, videoSource) {
if ( if (
(_this.config.videoSources.hasOwnProperty(videoType) === true) && (_this.config.videoSources.hasOwnProperty(videoType) === true) &&
...@@ -106,8 +160,27 @@ this.HTML5Video = (function () { ...@@ -106,8 +160,27 @@ this.HTML5Video = (function () {
} }
}); });
this.playerState = HTML5Video.PlayerState.UNSTARTED; // We should have at least one video source. Otherwise there is no point to continue.
if ((sourceStr.mp4 === ' ') && (sourceStr.webm === ' ') && (sourceStr.ogg === ' ')) {
return;
}
// Determine the starting and ending time for the video.
this.start = 0;
this.end = null;
if (config.hasOwnProperty('playerVars') === true) {
this.start = parseFloat(config.playerVars.start);
if ((isFinite(this.start) !== true) || (this.start < 0)) {
this.start = 0;
}
this.end = parseFloat(config.playerVars.end);
if ((isFinite(this.end) !== true) || (this.end < this.start)) {
this.end = null;
}
}
// Create HTML markup for the <video> element, populating it with sources from previous step.
this.videoEl = $( this.videoEl = $(
'<video style="width: 100%;">' + '<video style="width: 100%;">' +
sourceStr.mp4 + sourceStr.mp4 +
...@@ -116,30 +189,32 @@ this.HTML5Video = (function () { ...@@ -116,30 +189,32 @@ this.HTML5Video = (function () {
'</video>' '</video>'
); );
// Get the DOM element (to access the HTML5 video API), and set the player state to UNSTARTED.
// The player state is used by other parts of the VideoPlayer to detrermine what the video is
// currently doing.
this.video = this.videoEl[0]; this.video = this.videoEl[0];
this.playerState = HTML5Video.PlayerState.UNSTARTED;
// Attach a 'click' event on the <video> element. It will cause the video to pause/play.
this.videoEl.on('click', function (event) { this.videoEl.on('click', function (event) {
if (_this.playerState === HTML5Video.PlayerState.PAUSED) { if (_this.playerState === HTML5Video.PlayerState.PAUSED) {
_this.video.play(); _this.video.play();
_this.playerState = HTML5Video.PlayerState.PLAYING; _this.playerState = HTML5Video.PlayerState.PLAYING;
_this.callStateChangeCallback();
if ($.isFunction(_this.config.events.onStateChange) === true) {
_this.config.events.onStateChange({
'data': _this.playerState
});
}
} else if (_this.playerState === HTML5Video.PlayerState.PLAYING) { } else if (_this.playerState === HTML5Video.PlayerState.PLAYING) {
_this.video.pause(); _this.video.pause();
_this.playerState = HTML5Video.PlayerState.PAUSED; _this.playerState = HTML5Video.PlayerState.PAUSED;
_this.callStateChangeCallback();
if ($.isFunction(_this.config.events.onStateChange) === true) {
_this.config.events.onStateChange({
'data': _this.playerState
});
}
} }
}); });
// When the <video> tag has been processed by the browser, and it is ready for playback,
// notify other parts of the VideoPlayer, and initially pause the video.
//
// Also, at this time we can get the real duration of the video. Update the starting end ending
// points of the video. Note that first time, the video will start playing at the specified start time,
// and end playing at the specified end time. After it was paused, or when a seek operation happeded,
// the starting time and ending time will reset to the beginning and the end of the video respectively.
this.video.addEventListener('canplay', function () { this.video.addEventListener('canplay', function () {
_this.playerState = HTML5Video.PlayerState.PAUSED; _this.playerState = HTML5Video.PlayerState.PAUSED;
...@@ -155,96 +230,41 @@ this.HTML5Video = (function () { ...@@ -155,96 +230,41 @@ this.HTML5Video = (function () {
_this.config.events.onReady({}); _this.config.events.onReady({});
} }
}, false); }, false);
// Register the 'play' event.
this.video.addEventListener('play', function () { this.video.addEventListener('play', function () {
_this.playerState = HTML5Video.PlayerState.PLAYING; _this.playerState = HTML5Video.PlayerState.PLAYING;
_this.callStateChangeCallback();
if ($.isFunction(_this.config.events.onStateChange) === true) {
_this.config.events.onStateChange({
'data': _this.playerState
});
}
}, false); }, false);
// Register the 'pause' event.
this.video.addEventListener('pause', function () { this.video.addEventListener('pause', function () {
_this.playerState = HTML5Video.PlayerState.PAUSED; _this.playerState = HTML5Video.PlayerState.PAUSED;
_this.callStateChangeCallback();
if ($.isFunction(_this.config.events.onStateChange) === true) {
_this.config.events.onStateChange({
'data': _this.playerState
});
}
}, false); }, false);
// Register the 'ended' event.
this.video.addEventListener('ended', function () { this.video.addEventListener('ended', function () {
_this.playerState = HTML5Video.PlayerState.ENDED; _this.playerState = HTML5Video.PlayerState.ENDED;
_this.callStateChangeCallback();
if ($.isFunction(_this.config.events.onStateChange) === true) {
_this.config.events.onStateChange({
'data': _this.playerState
});
}
}, false); }, false);
// Register the 'timeupdate' event. This is the place where we control when the video ends.
// If an ending time was specified, then after the video plays through to this spot, pauses, we
// must make sure to update the ending time to the end of the video. This way, the user can watch
// any parts of it afterwards.
this.video.addEventListener('timeupdate', function (data) { this.video.addEventListener('timeupdate', function (data) {
if (_this.end < _this.video.currentTime) { if (_this.end < _this.video.currentTime) {
// When we call video.pause(), a 'pause' event will be formed, and we will catch it
// in another handler (see above).
_this.video.pause(); _this.video.pause();
_this.end = _this.video.duration;
} }
}, false); }, false);
// Place the <video> element on the page.
this.videoEl.appendTo(this.el.find('.video-player div')); this.videoEl.appendTo(this.el.find('.video-player div'));
} }
Player.prototype.pauseVideo = function () {
this.video.pause();
};
Player.prototype.seekTo = function (value) {
if ((typeof value === 'number') && (value <= this.video.duration) && (value >= 0)) {
this.start = 0;
this.end = this.video.duration;
this.video.currentTime = value;
}
};
Player.prototype.setVolume = function (value) {
if ((typeof value === 'number') && (value <= 100) && (value >= 0)) {
this.video.volume = value * 0.01;
}
};
Player.prototype.getCurrentTime = function () {
return this.video.currentTime;
};
Player.prototype.playVideo = function () {
this.video.play();
};
Player.prototype.getPlayerState = function () {
return this.playerState;
};
Player.prototype.getVolume = function () {
return this.video.volume;
};
Player.prototype.getDuration = function () {
return this.video.duration;
};
Player.prototype.setSpeed = function (value) {
var newSpeed;
newSpeed = parseFloat(value);
if (isFinite(newSpeed) === true) {
this.video.playbackRate = value;
}
};
Player.prototype.getAvailablePlaybackRates = function () {
return [0.75, 1.0, 1.25, 1.5];
};
return Player;
}()); }());
HTML5Video.PlayerState = { HTML5Video.PlayerState = {
......
...@@ -94,19 +94,16 @@ class @VideoPlayerAlpha extends SubviewAlpha ...@@ -94,19 +94,16 @@ class @VideoPlayerAlpha extends SubviewAlpha
when @PlayerState.UNSTARTED when @PlayerState.UNSTARTED
if @video.videoType is "youtube" if @video.videoType is "youtube"
availableSpeeds = @player.getAvailablePlaybackRates() availableSpeeds = @player.getAvailablePlaybackRates()
console.log @video.videos
if availableSpeeds.length > 1 if availableSpeeds.length > 1
baseSpeedSubs = @video.videos["1.0"] baseSpeedSubs = @video.videos["1.0"]
$.each @video.videos, (index, value) -> $.each @video.videos, (index, value) ->
delete _this.video.videos[index] delete _this.video.videos[index]
@video.speeds = []
$.each availableSpeeds, (index, value) -> $.each availableSpeeds, (index, value) ->
_this.video.videos[value.toFixed(2).replace(/\.00$/, ".0")] = baseSpeedSubs _this.video.videos[value.toFixed(2).replace(/\.00$/, ".0")] = baseSpeedSubs
_this.video.speeds.push value.toFixed(2).replace(/\.00$/, ".0")
@speedControl.reRender() @speedControl.reRender @video.speeds
@video.videoType = 'html5'
console.log "UNSTARTED. available speeds = "
console.log availableSpeeds
@onUnstarted() @onUnstarted()
when @PlayerState.PLAYING when @PlayerState.PLAYING
@onPlay() @onPlay()
...@@ -162,12 +159,12 @@ class @VideoPlayerAlpha extends SubviewAlpha ...@@ -162,12 +159,12 @@ class @VideoPlayerAlpha extends SubviewAlpha
if @video.videoType is 'youtube' if @video.videoType is 'youtube'
@currentTime = Time.convert(@currentTime, parseFloat(@currentSpeed()), newSpeed) @currentTime = Time.convert(@currentTime, parseFloat(@currentSpeed()), newSpeed)
newSpeed = parseFloat(newSpeed).toFixed(2).replace /\.00$/, '.0' newSpeed = parseFloat(newSpeed).toFixed(2).replace /\.00$/, '.0'
@video.setSpeed(newSpeed) @video.setSpeed newSpeed
if @video.videoType is 'youtube' if @video.videoType is 'youtube'
if @video.show_captions is true if @video.show_captions is true
@caption.currentSpeed = newSpeed @caption.currentSpeed = newSpeed
if @video.videoType is 'html5' if @video.videoType is 'html5'
@player.setSpeed(newSpeed) @player.setPlaybackRate newSpeed
else if @video.videoType is 'youtube' else if @video.videoType is 'youtube'
if @isPlaying() if @isPlaying()
@player.loadVideoById(@video.youtubeId(), @currentTime) @player.loadVideoById(@video.youtubeId(), @currentTime)
......
...@@ -24,20 +24,14 @@ class @VideoSpeedControlAlpha extends SubviewAlpha ...@@ -24,20 +24,14 @@ class @VideoSpeedControlAlpha extends SubviewAlpha
<ol class="video_speeds"></ol> <ol class="video_speeds"></ol>
</div> </div>
""" """
$.each @speeds, (index, speed) => $.each @speeds, (index, speed) =>
link = $('<a>').attr(href: "#").html("#{speed}x") link = $('<a>').attr(href: "#").html("#{speed}x")
@$('.video_speeds').prepend($('<li>').attr('data-speed', speed).html(link)) @$('.video_speeds').prepend($('<li>').attr('data-speed', speed).html(link))
@setSpeed(@currentSpeed) @setSpeed @currentSpeed
reRender: (newSpeeds) -> reRender: (newSpeeds) ->
@$('.video_speeds').empty() @$('.video_speeds').empty()
@speeds = newSpeeds
@speeds newSpeeds
console.log "Changing speeds"
console.log @speeds
$.each @speeds, (index, speed) => $.each @speeds, (index, speed) =>
link = $('<a>').attr(href: "#").html("#{speed}x") link = $('<a>').attr(href: "#").html("#{speed}x")
@$('.video_speeds').prepend($('<li>').attr('data-speed', speed).html(link)) @$('.video_speeds').prepend($('<li>').attr('data-speed', speed).html(link))
......
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