diff --git a/CHANGELOG.md b/CHANGELOG.md index 253ec6b803..eee3573f1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ CHANGELOG ========= ## HEAD (Unreleased) -_(none)_ +* @dmlap add the seekable property ([view](https://github.com/videojs/video.js/pull/2207)) -------------------- diff --git a/src/js/exports.js b/src/js/exports.js index b80c966ef2..c82be84b7a 100644 --- a/src/js/exports.js +++ b/src/js/exports.js @@ -101,6 +101,7 @@ goog.exportProperty(vjs.Player.prototype, 'remoteTextTracks', vjs.Player.prototy goog.exportProperty(vjs.Player.prototype, 'addTextTrack', vjs.Player.prototype.addTextTrack); goog.exportProperty(vjs.Player.prototype, 'addRemoteTextTrack', vjs.Player.prototype.addRemoteTextTrack); goog.exportProperty(vjs.Player.prototype, 'removeRemoteTextTrack', vjs.Player.prototype.removeRemoteTextTrack); +goog.exportProperty(vjs.Player.prototype, 'seekable', vjs.Player.prototype.removeRemoteTextTrack); goog.exportSymbol('videojs.MediaLoader', vjs.MediaLoader); goog.exportSymbol('videojs.TextTrackDisplay', vjs.TextTrackDisplay); diff --git a/src/js/lib.js b/src/js/lib.js index a1939ad576..71b806322c 100644 --- a/src/js/lib.js +++ b/src/js/lib.js @@ -617,6 +617,18 @@ vjs.round = function(num, dec) { * @private */ vjs.createTimeRange = function(start, end){ + if (start === undefined && end === undefined) { + return { + length: 0, + start: function() { + throw new Error('This TimeRanges object is empty'); + }, + end: function() { + throw new Error('This TimeRanges object is empty'); + } + }; + } + return { length: 1, start: function() { return start; }, diff --git a/src/js/media/flash.js b/src/js/media/flash.js index 85002d138a..6bf426887d 100644 --- a/src/js/media/flash.js +++ b/src/js/media/flash.js @@ -164,6 +164,15 @@ vjs.Flash.prototype['setPoster'] = function(){ // poster images are not handled by the Flash tech so make this a no-op }; +vjs.Flash.prototype.seekable = function() { + var duration = this.duration(); + if (duration === 0) { + // The SWF reports a duration of zero when the actual duration is unknown + return vjs.createTimeRange(); + } + return vjs.createTimeRange(0, this.duration()); +}; + vjs.Flash.prototype.buffered = function(){ return vjs.createTimeRange(0, this.el_.vjs_getProperty('buffered')); }; @@ -180,7 +189,7 @@ vjs.Flash.prototype.enterFullScreen = function(){ // Create setters and getters for attributes var api = vjs.Flash.prototype, readWrite = 'rtmpConnection,rtmpStream,preload,defaultPlaybackRate,playbackRate,autoplay,loop,mediaGroup,controller,controls,volume,muted,defaultMuted'.split(','), - readOnly = 'error,networkState,readyState,seeking,initialTime,duration,startOffsetTime,paused,played,seekable,ended,videoTracks,audioTracks,videoWidth,videoHeight'.split(','), + readOnly = 'error,networkState,readyState,seeking,initialTime,duration,startOffsetTime,paused,played,ended,videoTracks,audioTracks,videoWidth,videoHeight'.split(','), // Overridden: buffered, currentTime, currentSrc i; diff --git a/src/js/media/html5.js b/src/js/media/html5.js index 6c29f19e5e..f87d186283 100644 --- a/src/js/media/html5.js +++ b/src/js/media/html5.js @@ -348,6 +348,7 @@ vjs.Html5.prototype.setLoop = function(val){ this.el_.loop = val; }; vjs.Html5.prototype.error = function(){ return this.el_.error; }; vjs.Html5.prototype.seeking = function(){ return this.el_.seeking; }; +vjs.Html5.prototype.seekable = function(){ return this.el_.seekable; }; vjs.Html5.prototype.ended = function(){ return this.el_.ended; }; vjs.Html5.prototype.defaultMuted = function(){ return this.el_.defaultMuted; }; diff --git a/src/js/player.externs.js b/src/js/player.externs.js index 8f7ad4e849..075fc9d058 100644 --- a/src/js/player.externs.js +++ b/src/js/player.externs.js @@ -24,6 +24,7 @@ videojs.Player.prototype.load = function(){}; videojs.Player.prototype.canPlayType = function(){}; videojs.Player.prototype.readyState = function(){}; videojs.Player.prototype.seeking = function(){}; +videojs.Player.prototype.seekable = function(){}; videojs.Player.prototype.currentTime = function(){}; videojs.Player.prototype.remainingTime = function(){}; videojs.Player.prototype.startTime = function(){}; @@ -32,7 +33,6 @@ videojs.Player.prototype.paused = function(){}; videojs.Player.prototype.defaultPlaybackRate = function(){}; videojs.Player.prototype.playbackRate = function(){}; videojs.Player.prototype.played = function(){}; -videojs.Player.prototype.seekable = function(){}; videojs.Player.prototype.ended = function(){}; videojs.Player.prototype.autoplay = function(){}; videojs.Player.prototype.loop = function(){}; diff --git a/src/js/player.js b/src/js/player.js index 171629738f..899de475d9 100644 --- a/src/js/player.js +++ b/src/js/player.js @@ -1469,6 +1469,13 @@ vjs.Player.prototype.ended = function(){ return this.techGet('ended'); }; */ vjs.Player.prototype.seeking = function(){ return this.techGet('seeking'); }; +/** + * Returns the TimeRanges of the media that are currently available + * for seeking to. + * @return {TimeRanges} the seekable intervals of the media timeline + */ +vjs.Player.prototype.seekable = function(){ return this.techGet('seekable'); }; + // When the player is first initialized, trigger activity so components // like the control bar show themselves if needed vjs.Player.prototype.userActivity_ = true; diff --git a/test/unit/api.js b/test/unit/api.js index 5ee53cd9c5..e6854441b9 100644 --- a/test/unit/api.js +++ b/test/unit/api.js @@ -10,6 +10,7 @@ test('should be able to access expected player API methods', function() { ok(player.buffered, 'buffered exists'); ok(player.load, 'load exists'); ok(player.seeking, 'seeking exists'); + ok(player.seekable, 'seekable exists'); ok(player.currentTime, 'currentTime exists'); ok(player.duration, 'duration exists'); ok(player.paused, 'paused exists'); @@ -37,7 +38,6 @@ test('should be able to access expected player API methods', function() { // ok(player.defaultPlaybackRate, 'defaultPlaybackRate exists'); // ok(player.playbackRate, 'playbackRate exists'); // ok(player.played, 'played exists'); - // ok(player.seekable, 'seekable exists'); // ok(player.videoWidth, 'videoWidth exists'); // ok(player.videoHeight, 'videoHeight exists'); diff --git a/test/unit/flash.js b/test/unit/flash.js index 352bd3fc65..38051039f8 100644 --- a/test/unit/flash.js +++ b/test/unit/flash.js @@ -152,3 +152,37 @@ test('ready triggering before and after disposing the tech', function() { test('should have the source handler interface', function() { ok(vjs.Flash.registerSourceHandler, 'has the registerSourceHandler function'); }); + +test('seekable should be for the length of the loaded video', function() { + var player = PlayerTest.makePlayer(), + tech = new vjs.Flash(player, { + 'parentEl': player.el() + }), + duration = 23; + + // mock out duration + tech.el().vjs_getProperty = function(name) { + if (name === 'duration') { + return duration; + } + }; + equal(tech.seekable().length, 1, 'seekable is non-empty'); + equal(tech.seekable().start(0), 0, 'starts at zero'); + equal(tech.seekable().end(0), duration, 'ends at the duration'); +}); + +test('seekable should be empty if no video is loaded', function() { + var player = PlayerTest.makePlayer(), + tech = new vjs.Flash(player, { + 'parentEl': player.el() + }); + + // mock out duration + tech.el().vjs_getProperty = function(name) { + if (name === 'duration') { + return 0; + } + }; + + equal(tech.seekable().length, 0, 'seekable is empty'); +});