diff --git a/src/js/tech/flash.js b/src/js/tech/flash.js index 35fb9dd2e9..c23bb05531 100644 --- a/src/js/tech/flash.js +++ b/src/js/tech/flash.js @@ -164,9 +164,16 @@ class Flash extends Tech { * @method setCurrentTime */ setCurrentTime(time) { - this.lastSeekTarget_ = time; - this.el_.vjs_setProperty('currentTime', time); - super.setCurrentTime(); + let seekable = this.seekable(); + if (seekable.length) { + // clamp to the current seekable range + time = time > seekable.start(0) ? time : seekable.start(0); + time = time < seekable.end(seekable.length - 1) ? time : seekable.end(seekable.length - 1); + + this.lastSeekTarget_ = time; + this.el_.vjs_setProperty('currentTime', time); + super.setCurrentTime(); + } } /** diff --git a/src/js/tech/tech.js b/src/js/tech/tech.js index 4e7e88f62d..f2079d4f01 100644 --- a/src/js/tech/tech.js +++ b/src/js/tech/tech.js @@ -565,6 +565,16 @@ Tech.withSourceHandlers = function(_Tech){ this.sourceHandler_ = sh.handleSource(source, this); this.on('dispose', this.disposeSourceHandler); + this.originalSeekable_ = this.seekable; + // when a source handler is registered, prefer its implementation of + // seekable when present. + this.seekable = function() { + if (this.sourceHandler_ && this.sourceHandler_.seekable) { + return this.sourceHandler_.seekable(); + } + return this.originalSeekable_.call(this); + }; + return this; }; @@ -574,6 +584,7 @@ Tech.withSourceHandlers = function(_Tech){ _Tech.prototype.disposeSourceHandler = function(){ if (this.sourceHandler_ && this.sourceHandler_.dispose) { this.sourceHandler_.dispose(); + this.seekable = this.originalSeekable_; } }; diff --git a/test/unit/tech/flash.test.js b/test/unit/tech/flash.test.js index 81680f94c4..085d6c514c 100644 --- a/test/unit/tech/flash.test.js +++ b/test/unit/tech/flash.test.js @@ -1,4 +1,5 @@ import Flash from '../../../src/js/tech/flash.js'; +import { createTimeRange } from '../../../src/js/utils/time-ranges.js'; import document from 'global/document'; q.module('Flash'); @@ -30,14 +31,17 @@ test('currentTime', function() { // Mock out a Flash instance to avoid creating the swf object let mockFlash = { el_: { - vjs_setProperty: function(prop, val){ + vjs_setProperty(prop, val){ setPropVal = val; }, - vjs_getProperty: function(){ + vjs_getProperty(){ return getPropVal; } }, - seeking: function(){ + seekable(){ + return createTimeRange(5, 1000); + }, + seeking(){ return seeking; } }; @@ -57,6 +61,15 @@ test('currentTime', function() { result = getCurrentTime.call(mockFlash); equal(result, 20, 'currentTime is retrieved from the lastSeekTarget while seeking'); notEqual(result, getPropVal, 'currentTime is not retrieved from the element while seeking'); + + // clamp seeks to seekable + setCurrentTime.call(mockFlash, 1001); + result = getCurrentTime.call(mockFlash); + equal(result, mockFlash.seekable().end(0), 'clamped to the seekable end'); + + setCurrentTime.call(mockFlash, 1); + result = getCurrentTime.call(mockFlash); + equal(result, mockFlash.seekable().start(0), 'clamped to the seekable start'); }); test('dispose removes the object element even before ready fires', function() { @@ -141,3 +154,10 @@ test('seekable', function() { result = seekable.call(mockFlash); equal(result.length, mockFlash.duration_, 'seekable is empty with a zero duration'); }); + +// fake out the interaction but leave all the other logic intact +class MockFlash extends Flash { + constructor() { + super({}); + } +} diff --git a/test/unit/tech/tech.test.js b/test/unit/tech/tech.test.js index d53fb374ee..09871fc547 100644 --- a/test/unit/tech/tech.test.js +++ b/test/unit/tech/tech.test.js @@ -1,6 +1,7 @@ var noop = function() {}, clock, oldTextTracks; import Tech from '../../../src/js/tech/tech.js'; +import { createTimeRange } from '../../../src/js/utils/time-ranges.js'; q.module('Media Tech', { 'setup': function() { @@ -97,7 +98,7 @@ test('dispose() should stop time tracking', function() { ok(true, 'no exception was thrown'); }); -test('should add the source hanlder interface to a tech', function(){ +test('should add the source handler interface to a tech', function(){ var sourceA = { src: 'foo.mp4', type: 'video/mp4' }; var sourceB = { src: 'no-support', type: 'no-support' }; @@ -176,7 +177,7 @@ test('should add the source hanlder interface to a tech', function(){ ok(disposeCalled, 'the handler dispose method was called when the tech was disposed'); }); -test('should handle unsupported sources with the source hanlder API', function(){ +test('should handle unsupported sources with the source handler API', function(){ // Define a new tech class var MyTech = Tech.extend(); // Extend Tech with source handlers @@ -197,7 +198,40 @@ test('should track whether a video has played', function() { let tech = new Tech(); equal(tech.played().length, 0, 'starts with zero length'); - tech.trigger('playing'); equal(tech.played().length, 1, 'has length after playing'); }); + +test('delegates seekable to the source handler', function(){ + let MyTech = Tech.extend({ + seekable: function() { + throw new Error('You should not be calling me!'); + } + }); + Tech.withSourceHandlers(MyTech); + + let seekableCount = 0; + let handler = { + seekable: function() { + seekableCount++; + return createTimeRange(0, 0); + } + }; + + MyTech.registerSourceHandler({ + canHandleSource: function() { + return true; + }, + handleSource: function(source, tech) { + return handler; + } + }); + + let tech = new MyTech(); + tech.setSource({ + src: 'example.mp4', + type: 'video/mp4' + }); + tech.seekable(); + equal(seekableCount, 1, 'called the source handler'); +});