diff --git a/dashboard-ui/css/site.css b/dashboard-ui/css/site.css
index 3a837e0c08..c66fee35d2 100644
--- a/dashboard-ui/css/site.css
+++ b/dashboard-ui/css/site.css
@@ -814,19 +814,28 @@ progress {
margin-right: 20px;
display: inline-block;
position: relative;
+ width: 270px;
}
.mediaButton img {
height: 28px;
}
-.itemVideo {
+.itemVideo, .itemVideo.video-js {
position: absolute;
z-index: 99998;
height: auto;
- width: 180px;
+ /*width: 180px;*/
bottom: -5px;
}
+.vjs-quality-button {
+ padding: 0 0.6em !important;
+ width: auto !important;
+}
+.vjs-quality-button div {
+ width: auto !important;
+ background: none !important;
+}
@media all and (min-width: 650px) {
diff --git a/dashboard-ui/scripts/Extensions.js b/dashboard-ui/scripts/Extensions.js
index 3621e341bf..6d8756c3c9 100644
--- a/dashboard-ui/scripts/Extensions.js
+++ b/dashboard-ui/scripts/Extensions.js
@@ -328,4 +328,355 @@ function parseISO8601Date(s, toLocal) {
}
});
-})(jQuery, window);
\ No newline at end of file
+})(jQuery, window);
+
+
+/*
+ JS for the quality selector in video.js player
+ */
+
+/*
+ Define the base class for the quality selector button.
+ Most of this code is copied from the _V_.TextTrackButton
+ class.
+
+ https://github.com/zencoder/video-js/blob/master/src/tracks.js#L560)
+ */
+_V_.ResolutionSelector = _V_.Button.extend({
+
+ kind: "quality",
+ className: "vjs-quality-button",
+
+ init: function(player, options) {
+
+ this._super(player, options);
+
+ // Save the starting resolution as a property of the player object
+ player.options.currentResolution = this.buttonText;
+
+ this.menu = this.createMenu();
+
+ if (this.items.length === 0) {
+ this.hide();
+ }
+ },
+
+ createMenu: function() {
+
+ var menu = new _V_.Menu(this.player);
+
+ // Add a title list item to the top
+ menu.el.appendChild(_V_.createElement("li", {
+ className: "vjs-menu-title",
+ innerHTML: _V_.uc(this.kind)
+ }));
+
+ this.items = this.createItems();
+
+ // Add menu items to the menu
+ this.each(this.items, function(item){
+ menu.addItem(item);
+ });
+
+ // Add list to element
+ this.addComponent(menu);
+
+ return menu;
+ },
+
+ // Override the default _V_.Button createElement so the button text isn't hidden
+ createElement: function(type, attrs) {
+
+ // Add standard Aria and Tabindex info
+ attrs = _V_.merge({
+ className: this.buildCSSClass(),
+ innerHTML: '
' + this.buttonText + '
',
+ role: "button",
+ tabIndex: 0
+ }, attrs);
+
+ return this._super(type, attrs);
+ },
+
+ // Create a menu item for each text track
+ createItems: function() {
+
+ var items = [];
+
+ this.each( this.availableRes, function( res ) {
+
+ items.push( new _V_.ResolutionMenuItem( this.player, {
+
+ label: res[0].res,
+ src: res
+ }));
+ });
+
+ return items;
+ },
+
+ buildCSSClass: function() {
+
+ return this.className + " vjs-menu-button " + this._super();
+ },
+
+ // Focus - Add keyboard functionality to element
+ onFocus: function() {
+
+ // Show the menu, and keep showing when the menu items are in focus
+ this.menu.lockShowing();
+ this.menu.el.style.display = "block";
+
+ // When tabbing through, the menu should hide when focus goes from the last menu item to the next tabbed element.
+ _V_.one(this.menu.el.childNodes[this.menu.el.childNodes.length - 1], "blur", this.proxy(function() {
+
+ this.menu.unlockShowing();
+ }));
+ },
+
+ // Can't turn off list display that we turned on with focus, because list would go away.
+ onBlur: function(){},
+
+ onClick: function() {
+
+ /*
+ When you click the button it adds focus, which will show the menu indefinitely.
+ So we'll remove focus when the mouse leaves the button.
+ Focus is needed for tab navigation.
+ */
+ this.one( 'mouseout', this.proxy(function() {
+
+ this.menu.unlockShowing();
+ this.el.blur();
+ }));
+ }
+});
+
+/*
+ Define the base class for the quality menu items
+ */
+_V_.ResolutionMenuItem = _V_.MenuItem.extend({
+
+ init: function(player, options){
+
+ // Modify options for parent MenuItem class's init.
+ options.selected = ( options.label === player.options.currentResolution );
+ this._super( player, options );
+
+ this.player.addEvent( 'changeRes', _V_.proxy( this, this.update ) );
+ },
+
+ onClick: function() {
+
+ // Check that we are changing to a new quality (not the one we are already on)
+ if ( this.options.label === this.player.options.currentResolution )
+ return;
+
+ var resolutions = new Array();
+ resolutions['high'] = 500000;
+ resolutions['medium'] = 250000;
+ resolutions['low'] = 50000;
+
+ var current_time = this.player.currentTime();
+
+ // Set the button text to the newly chosen quality
+ jQuery( this.player.controlBar.el ).find( '.vjs-quality-text' ).html( this.options.label );
+
+ // Change the source and make sure we don't start the video over
+ var currentSrc = $("#"+this.options.src[0].vid_id).find('video').attr("src");
+ var newSrc = currentSrc.replace("videoBitrate="+resolutions[this.player.options.currentResolution],"videoBitrate="+resolutions[this.options.src[0].res]);
+
+ if (this.player.duration() == "Infinity") {
+ if (currentSrc.indexOf("StartTimeTicks") >= 0) {
+ var startTimeTicks = newSrc.match(new RegExp("StartTimeTicks=[0-9]+","g"));
+ var start_time = startTimeTicks[0].replace("StartTimeTicks=","");
+
+ newSrc = newSrc.replace(new RegExp("StartTimeTicks=[0-9]+","g"),"StartTimeTicks="+(parseInt(start_time)+(10000000*current_time)));
+ }else {
+ newSrc += "&StartTimeTicks="+10000000*current_time;
+ }
+
+ this.player.src( newSrc ).one( 'loadedmetadata', function() {
+ this.play();
+ });
+ }else {
+ this.player.src( newSrc ).one( 'loadedmetadata', function() {
+ this.currentTime( current_time );
+ this.play();
+ });
+ }
+
+ // Save the newly selected resolution in our player options property
+ this.player.options.currentResolution = this.options.label;
+
+ // Update the classes to reflect the currently selected resolution
+ this.player.triggerEvent( 'changeRes' );
+ },
+
+ update: function() {
+
+ if ( this.options.label === this.player.options.currentResolution ) {
+ this.selected( true );
+ } else {
+ this.selected( false );
+ }
+ }
+});
+
+
+/*
+ JS for the chapter selector in video.js player
+ */
+
+/*
+ Define the base class for the chapter selector button.
+ */
+_V_.ChapterSelector = _V_.Button.extend({
+
+ kind: "chapter",
+ className: "vjs-chapter-button",
+
+ init: function(player, options) {
+
+ this._super(player, options);
+
+ this.menu = this.createMenu();
+
+ if (this.items.length === 0) {
+ this.hide();
+ }
+ },
+
+ createMenu: function() {
+
+ var menu = new _V_.Menu(this.player);
+
+ // Add a title list item to the top
+ menu.el.appendChild(_V_.createElement("li", {
+ className: "vjs-menu-title",
+ innerHTML: _V_.uc(this.kind)
+ }));
+
+ this.items = this.createItems();
+
+ // Add menu items to the menu
+ this.each(this.items, function(item){
+ menu.addItem(item);
+ });
+
+ // Add list to element
+ this.addComponent(menu);
+
+ return menu;
+ },
+
+ // Override the default _V_.Button createElement so the button text isn't hidden
+ createElement: function(type, attrs) {
+
+ // Add standard Aria and Tabindex info
+ attrs = _V_.merge({
+ className: this.buildCSSClass(),
+ innerHTML: '' + this.buttonText + '
',
+ role: "button",
+ tabIndex: 0
+ }, attrs);
+
+ return this._super(type, attrs);
+ },
+
+ // Create a menu item for each chapter
+ createItems: function() {
+
+ var items = [];
+
+ this.each( this.Chapters, function( chapter ) {
+
+ items.push( new _V_.ChapterMenuItem( this.player, {
+ label: chapter[0].Name,
+ src: chapter
+ }));
+ });
+
+ return items;
+ },
+
+ buildCSSClass: function() {
+
+ return this.className + " vjs-menu-button " + this._super();
+ },
+
+ // Focus - Add keyboard functionality to element
+ onFocus: function() {
+
+ // Show the menu, and keep showing when the menu items are in focus
+ this.menu.lockShowing();
+ this.menu.el.style.display = "block";
+
+ // When tabbing through, the menu should hide when focus goes from the last menu item to the next tabbed element.
+ _V_.one(this.menu.el.childNodes[this.menu.el.childNodes.length - 1], "blur", this.proxy(function() {
+
+ this.menu.unlockShowing();
+ }));
+ },
+
+ // Can't turn off list display that we turned on with focus, because list would go away.
+ onBlur: function(){},
+
+ onClick: function() {
+
+ /*
+ When you click the button it adds focus, which will show the menu indefinitely.
+ So we'll remove focus when the mouse leaves the button.
+ Focus is needed for tab navigation.
+ */
+ this.one( 'mouseout', this.proxy(function() {
+
+ this.menu.unlockShowing();
+ this.el.blur();
+ }));
+ }
+});
+
+/*
+ Define the base class for the chapter menu items
+ */
+_V_.ChapterMenuItem = _V_.MenuItem.extend({
+
+ init: function(player, options){
+
+ // Modify options for parent MenuItem class's init.
+ //options.selected = ( options.label === player.options.currentResolution );
+ this._super( player, options );
+
+ this.player.addEvent( 'changeChapter', _V_.proxy( this, this.update ) );
+ },
+
+ onClick: function() {
+
+ // Set the button text to the newly chosen chapter
+ //jQuery( this.player.controlBar.el ).find( '.vjs-chapter-text' ).html( this.options.label );
+
+ if (this.player.duration() == "Infinity") {
+ var currentSrc = $("#"+this.options.src[0].vid_id).find('video').attr("src");
+
+ if (currentSrc.indexOf("StartTimeTicks") >= 0) {
+ var newSrc = currentSrc.replace(new RegExp("StartTimeTicks=[0-9]+","g"),"StartTimeTicks="+this.options.src[0].StartPositionTicks);
+ }else {
+ var newSrc = currentSrc += "&StartTimeTicks="+this.options.src[0].StartPositionTicks;
+ }
+
+ this.player.src( newSrc ).one( 'loadedmetadata', function() {
+ this.play();
+ });
+ }else {
+ //figure out the time from ticks
+ var current_time = parseFloat(this.options.src[0].StartPositionTicks)/10000000;
+
+ this.player.currentTime( current_time );
+ }
+ },
+
+ update: function() {
+ }
+});
+
diff --git a/dashboard-ui/scripts/MediaPlayer.js b/dashboard-ui/scripts/MediaPlayer.js
index 8b3043ba5c..4bdda3b535 100644
--- a/dashboard-ui/scripts/MediaPlayer.js
+++ b/dashboard-ui/scripts/MediaPlayer.js
@@ -68,7 +68,7 @@
}
},
- playAudio: function (items) {
+ playAudio: function (items, params) {
var item = items[0];
var baseParams = {
@@ -76,6 +76,8 @@
audioBitrate: 128000
};
+ $.extend(baseParams, params);
+
var mp3Url = ApiClient.getUrl('Audio/' + item.Id + '/stream.mp3', $.extend({}, baseParams, {
audioCodec: 'mp3'
}));
@@ -123,44 +125,51 @@
maxHeight: screenHeight
};
- var mp4VideoUrl = ApiClient.getUrl('Videos/' + item.Id + '/stream.mp4', $.extend({}, baseParams, {
- videoCodec: 'h264',
- audioCodec: 'aac'
- }));
+ //TODO if you press "stop" button on the nowPlayingBar and restart the same video without refreshing the page
+ //there is an issue since VideoJS is still loaded.
- var tsVideoUrl = ApiClient.getUrl('Videos/' + item.Id + '/stream.ts', $.extend({}, baseParams, {
- videoCodec: 'h264',
- audioCodec: 'aac'
- }));
+ var html = '';
- var webmVideoUrl = ApiClient.getUrl('Videos/' + item.Id + '/stream.webm', $.extend({}, baseParams, {
- videoCodec: 'vpx',
- audioCodec: 'Vorbis'
- }));
+ var nowPlayingBar = $('#nowPlayingBar');
- var hlsVideoUrl = ApiClient.getUrl('Videos/' + item.Id + '/stream.m3u8', $.extend({}, baseParams, {
- videoCodec: 'h264',
- audioCodec: 'aac'
- }));
+ $('#mediaElement', nowPlayingBar).html(html).show();
- var ogvVideoUrl = ApiClient.getUrl('Videos/' + item.Id + '/stream.ogv', $.extend({}, baseParams, {
- videoCodec: 'theora',
- audioCodec: 'Vorbis'
- }));
+ _V_("videoWindow", {'controls': true, 'autoplay': true, 'preload': 'auto'}, function(){
- var html = '';
- html += ' element we are setting up the
+ videojs video for.
+ */
+ setup_video : function( $video, item ) {
+
+ var vid_id = $video.attr( 'id' ),
+ available_res = ['high','medium','low'],
+ default_res,
+ vjs_sources = [], // This will be an array of arrays of objects, see the video.js api documentation for myPlayer.src()
+ vjs_source = {},
+ vjs_chapters = [], // This will be an array of arrays of objects, see the video.js api documentation for myPlayer.src()
+ vjs_chapter = {};
+
+ // Determine this video's default res (it might not have the globally determined default available)
+ default_res = available_res[0];
+
+ // Put together the videojs source arrays for each available resolution
+ $.each( available_res, function( i, res ) {
+
+ vjs_sources[i] = [];
+
+ vjs_source = {};
+ vjs_source.res = res;
+ vjs_source.vid_id = vid_id;
+
+ vjs_sources[i].push( vjs_source );
+
+ });
+
+ _V_.ResolutionSelectorButton = _V_.ResolutionSelector.extend({
+ buttonText: default_res,
+ availableRes: vjs_sources
+ });
+
+ // Add the resolution selector button.
+ _V_.merge( _V_.ControlBar.prototype.options.components, { ResolutionSelectorButton : {} } );
+
+ //chceck if chapters exist and add chapter selector
+ if (item.Chapters.length > 0) {
+ // Put together the videojs source arrays for each available chapter
+ $.each( item.Chapters, function( i, chapter ) {
+
+ vjs_chapters[i] = [];
+
+ vjs_chapter = {};
+ vjs_chapter.Name = chapter.Name;
+ vjs_chapter.StartPositionTicks = chapter.StartPositionTicks;
+ vjs_chapter.vid_id = vid_id;
+
+ vjs_chapters[i].push( vjs_chapter );
+
+ });
+
+ _V_.ChapterSelectorButton = _V_.ChapterSelector.extend({
+ buttonText: '',
+ Chapters: vjs_chapters
+ });
+
+ // Add the chapter selector button.
+ _V_.merge( _V_.ControlBar.prototype.options.components, { ChapterSelectorButton : {} } );
+ }
+
+ }
};
\ No newline at end of file