1
0
mirror of https://github.com/videojs/video.js.git synced 2024-12-25 02:42:10 +02:00

Added support for playbackRate switching. closes #1132

This commit is contained in:
Artem Suschev 2014-05-13 14:02:02 -07:00 committed by Steve Heffernan
parent 047bd8f618
commit 8dfe0a4d06
15 changed files with 229 additions and 5 deletions

View File

@ -18,6 +18,7 @@ CHANGELOG
* Fixed compilation failures with LESS v1.7.0 and GRUNT v0.4.4 [[view](https://github.com/videojs/video.js/pull/1180)]
* Added better error handling across the library [[view](https://github.com/videojs/video.js/pull/1197)]
* Updated captions/subtiles file fetching to support cross-origin requests in older IE browsers [[view](https://github.com/videojs/video.js/pull/1095)]
* Added support for playback rate switching [[view](https://github.com/videojs/video.js/pull/1132)]
--------------------

View File

@ -35,6 +35,7 @@ var sourceFiles = [
"src/js/control-bar/volume-control.js",
"src/js/control-bar/mute-toggle.js",
"src/js/control-bar/volume-menu-button.js",
"src/js/control-bar/playback-rate-menu-button.js",
"src/js/poster.js",
"src/js/loading-spinner.js",
"src/js/big-play-button.js",

View File

@ -25,7 +25,7 @@
<source src="http://video-js.zencoder.com/oceans-clip.webm" type='video/webm'>
<source src="http://video-js.zencoder.com/oceans-clip.ogv" type='video/ogg'>
<track kind="captions" src="../build/demo-files/demo.captions.vtt" srclang="en" label="English"></track><!-- Tracks need an ending tag thanks to IE9 -->
<p class="vjs-no-js">To view this video please enable JavaScript, and consider upgrading to a web browser that <a href="http://videojs.com/html5-video-support/" target="_blank">supports HTML5 video</a>
<p class="vjs-no-js">To view this video please enable JavaScript, and consider upgrading to a web browser that <a href="http://videojs.com/html5-video-support/" target="_blank">supports HTML5 video</a></p>
</video>
<script>

View File

@ -265,6 +265,27 @@ fonts to show/hide properly.
content: @pause-icon;
}
/* Playback toggle
--------------------------------------------------------------------------------
*/
.vjs-default-skin .vjs-playback-rate .vjs-playback-rate-value {
font-size: 1.5em;
line-height: 2;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
text-align: center;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5);
}
.vjs-default-skin .vjs-playback-rate.vjs-menu-button .vjs-menu .vjs-menu-content {
width: 4em;
left: -2em;
list-style: none;
}
/* Volume/Mute
-------------------------------------------------------------------------------- */
.vjs-default-skin .vjs-mute-control,

View File

@ -20,8 +20,9 @@ vjs.ControlBar.prototype.options_ = {
'progressControl': {},
'fullscreenToggle': {},
'volumeControl': {},
'muteToggle': {}
// 'volumeMenuButton': {}
'muteToggle': {},
// 'volumeMenuButton': {},
'playbackRateMenuButton': {}
}
};

View File

@ -0,0 +1,129 @@
/**
* The component for controlling the playback rate
*
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.PlaybackRateMenuButton = vjs.MenuButton.extend({
/** @constructor */
init: function(player, options){
vjs.MenuButton.call(this, player, options);
this.updateVisibility();
this.updateLabel();
player.on('loadstart', vjs.bind(this, this.updateVisibility));
player.on('ratechange', vjs.bind(this, this.updateLabel));
}
});
vjs.PlaybackRateMenuButton.prototype.createEl = function(){
var el = vjs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-playback-rate vjs-menu-button vjs-control',
innerHTML: '<div class="vjs-control-content"><span class="vjs-control-text">Playback Rate</span></div>'
});
this.labelEl_ = vjs.createEl('div', {
className: 'vjs-playback-rate-value',
innerHTML: 1.0
});
el.appendChild(this.labelEl_);
return el;
};
// Menu creation
vjs.PlaybackRateMenuButton.prototype.createMenu = function(){
var menu = new vjs.Menu(this.player());
var rates = this.player().options().playbackRates;
if (rates) {
for (var i = rates.length - 1; i >= 0; i--) {
menu.addChild(
new vjs.PlaybackRateMenuItem(this.player(), {rate: rates[i] + 'x'})
);
};
}
return menu;
};
vjs.PlaybackRateMenuButton.prototype.updateARIAAttributes = function(){
// Current playback rate
this.el().setAttribute('aria-valuenow', this.player().playbackRate());
};
vjs.PlaybackRateMenuButton.prototype.onClick = function(){
// select next rate option
var currentRate = this.player().playbackRate();
var rates = this.player().options().playbackRates;
// this will select first one if the last one currently selected
var newRate = rates[0];
for (var i = 0; i <rates.length ; i++) {
if (rates[i] > currentRate) {
newRate = rates[i];
break;
}
};
this.player().playbackRate(newRate);
};
vjs.PlaybackRateMenuButton.prototype.playbackRateSupported = function(){
return this.player().tech
&& this.player().tech.features['playbackRate']
&& this.player().options().playbackRates
&& this.player().options().playbackRates.length > 0
;
};
/**
* Hide playback rate controls when they're no playback rate options to select
*/
vjs.PlaybackRateMenuButton.prototype.updateVisibility = function(){
if (this.playbackRateSupported()) {
this.removeClass('vjs-hidden');
} else {
this.addClass('vjs-hidden');
}
};
/**
* Update button label when rate changed
*/
vjs.PlaybackRateMenuButton.prototype.updateLabel = function(){
if (this.playbackRateSupported()) {
this.labelEl_.innerHTML = this.player().playbackRate() + 'x';
}
};
/**
* The specific menu item type for selecting a playback rate
*
* @constructor
*/
vjs.PlaybackRateMenuItem = vjs.MenuItem.extend({
contentElType: 'button',
/** @constructor */
init: function(player, options){
var label = this.label = options['rate'];
var rate = this.rate = parseFloat(label, 10);
// Modify options for parent MenuItem class's init.
options['label'] = label;
options['selected'] = rate === 1;
vjs.MenuItem.call(this, player, options);
this.player().on('ratechange', vjs.bind(this, this.update));
}
});
vjs.PlaybackRateMenuItem.prototype.onClick = function(){
vjs.MenuItem.prototype.onClick.call(this);
this.player().playbackRate(this.rate);
};
vjs.PlaybackRateMenuItem.prototype.update = function(){
this.selected(this.player().playbackRate() == this.rate);
};

View File

@ -88,6 +88,11 @@ vjs.options = {
// defaultVolume: 0.85,
'defaultVolume': 0.00, // The freakin seaguls are driving me crazy!
// default playback rates
'playbackRates': [],
// Add playback rate selection by adding rates
// 'playbackRates': [0.5, 1, 1.5, 2],
// Included control sets
'children': {
'mediaLoader': {},

View File

@ -102,6 +102,7 @@ goog.exportSymbol('videojs.PosterImage', vjs.PosterImage);
goog.exportSymbol('videojs.Menu', vjs.Menu);
goog.exportSymbol('videojs.MenuItem', vjs.MenuItem);
goog.exportSymbol('videojs.MenuButton', vjs.MenuButton);
goog.exportSymbol('videojs.PlaybackRateMenuButton', vjs.PlaybackRateMenuButton);
goog.exportProperty(vjs.MenuButton.prototype, 'createItems', vjs.MenuButton.prototype.createItems);
goog.exportProperty(vjs.TextTrackButton.prototype, 'createItems', vjs.TextTrackButton.prototype.createItems);
goog.exportProperty(vjs.ChaptersButton.prototype, 'createItems', vjs.ChaptersButton.prototype.createItems);
@ -136,6 +137,8 @@ goog.exportProperty(vjs.Html5.prototype, 'setAutoplay', vjs.Html5.prototype.setA
goog.exportProperty(vjs.Html5.prototype, 'setLoop', vjs.Html5.prototype.setLoop);
goog.exportProperty(vjs.Html5.prototype, 'enterFullScreen', vjs.Html5.prototype.enterFullScreen);
goog.exportProperty(vjs.Html5.prototype, 'exitFullScreen', vjs.Html5.prototype.exitFullScreen);
goog.exportProperty(vjs.Html5.prototype, 'playbackRate', vjs.Html5.prototype.playbackRate);
goog.exportProperty(vjs.Html5.prototype, 'setPlaybackRate', vjs.Html5.prototype.setPlaybackRate);
goog.exportSymbol('videojs.Flash', vjs.Flash);
goog.exportProperty(vjs.Flash, 'isSupported', vjs.Flash.isSupported);

View File

@ -317,7 +317,6 @@ vjs.Flash.prototype.enterFullScreen = function(){
return false;
};
// 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(','),

View File

@ -15,6 +15,9 @@ vjs.Html5 = vjs.MediaTechController.extend({
// volume cannot be changed from 1 on iOS
this.features['volumeControl'] = vjs.Html5.canControlVolume();
// just in case; or is it excessively...
this.features['playbackRate'] = vjs.Html5.canControlPlaybackRate();
// In iOS, if you move a video element in the DOM, it breaks video playback.
this.features['movingMediaElementInDOM'] = !vjs.IS_IOS;
@ -242,6 +245,9 @@ vjs.Html5.prototype.seeking = function(){ return this.el_.seeking; };
vjs.Html5.prototype.ended = function(){ return this.el_.ended; };
vjs.Html5.prototype.defaultMuted = function(){ return this.el_.defaultMuted; };
vjs.Html5.prototype.playbackRate = function(){ return this.el_.playbackRate; };
vjs.Html5.prototype.setPlaybackRate = function(val){ this.el_.playbackRate = val; };
/* HTML5 Support Testing ---------------------------------------------------- */
vjs.Html5.isSupported = function(){
@ -274,6 +280,12 @@ vjs.Html5.canControlVolume = function(){
return volume !== vjs.TEST_VID.volume;
};
vjs.Html5.canControlPlaybackRate = function(){
var playbackRate = vjs.TEST_VID.playbackRate;
vjs.TEST_VID.playbackRate = (playbackRate / 2) + 0.1;
return playbackRate !== vjs.TEST_VID.playbackRate;
};
// HTML5 Feature detection and Device Fixes --------------------------------- //
(function() {
var canPlayType,

View File

@ -152,6 +152,7 @@ vjs.MediaTechController.prototype.features = {
// Resizing plugins using request fullscreen reloads the plugin
'fullscreenResize': false,
'playbackRate': false,
// Optional events that we can manually mimic with timers
// currently not triggered by video-js-swf

View File

@ -1449,6 +1449,20 @@ vjs.Player.prototype.listenForUserActivity = function(){
});
};
vjs.Player.prototype.playbackRate = function(rate) {
if (rate !== undefined) {
this.techCall('setPlaybackRate', rate);
return this;
}
if (this.tech && this.tech.features && this.tech.features['playbackRate']) {
return this.techGet('playbackRate');
} else {
return 1.0;
}
};
// Methods to add support for
// networkState: function(){ return this.techCall('networkState'); },
// readyState: function(){ return this.techCall('readyState'); },
@ -1461,7 +1475,6 @@ vjs.Player.prototype.listenForUserActivity = function(){
// videoWidth: function(){ return this.techCall('videoWidth'); },
// videoHeight: function(){ return this.techCall('videoHeight'); },
// defaultPlaybackRate: function(){ return this.techCall('defaultPlaybackRate'); },
// playbackRate: function(){ return this.techCall('playbackRate'); },
// mediaGroup: function(){ return this.techCall('mediaGroup'); },
// controller: function(){ return this.techCall('controller'); },
// defaultMuted: function(){ return this.techCall('defaultMuted'); }

View File

@ -27,6 +27,7 @@ test('should be able to access expected player API methods', function() {
ok(player.textTracks, 'textTracks exists');
ok(player.requestFullScreen, 'requestFullScreen exists');
ok(player.cancelFullScreen, 'cancelFullScreen exists');
ok(player.playbackRate, 'playbackRate exists');
// Unsupported Native HTML5 Methods
// ok(player.canPlayType, 'canPlayType exists');
@ -138,6 +139,7 @@ test('should export useful components to the public', function () {
ok(videojs.Menu, 'Menu should be public');
ok(videojs.MenuItem, 'MenuItem should be public');
ok(videojs.MenuButton, 'MenuButton should be public');
ok(videojs.PlaybackRateMenuButton, 'PlaybackRateMenuButton should be public');
ok(videojs.util, 'util namespace should be public');
ok(videojs.util.mergeOptions, 'mergeOptions should be public');

View File

@ -103,3 +103,12 @@ test('calculateDistance should use changedTouches, if available', function() {
equal(slider.calculateDistance(event), 0.5, 'we should have touched exactly in the center, so, the ratio should be half');
});
test('should hide playback rate control if it\'s not supported', function(){
expect(1);
var player = PlayerTest.makePlayer();
var playbackRate = new vjs.PlaybackRateMenuButton(player);
ok(playbackRate.el().className.indexOf('vjs-hidden') >= 0, 'playbackRate is not hidden');
});

View File

@ -92,3 +92,30 @@ test('should return a maybe for mp4 on OLD ANDROID', function() {
vjs.IS_OLD_ANDROID = isOldAndroid;
vjs.Html5.unpatchCanPlayType();
});
test('test playbackRate', function() {
var el, player, playbackRate, tech;
el = document.createElement('div');
el.innerHTML = '<div />';
player = {
id: function(){ return 'id'; },
el: function(){ return el; },
options_: {},
options: function(){ return {}; },
controls: function(){ return false; },
usingNativeControls: function(){ return false; },
on: function(){ return this; },
ready: function(){}
};
tech = new vjs.Html5(player, {});
tech.createEl();
tech.el_.playbackRate = 1.25;
strictEqual(tech.playbackRate(), 1.25);
tech['setPlaybackRate'](0.75);
strictEqual(tech.playbackRate(), 0.75);
});