diff --git a/dashboard-ui/css/images/media/chapters.png b/dashboard-ui/css/images/media/chapters.png new file mode 100644 index 0000000000..11c4ccfe51 Binary files /dev/null and b/dashboard-ui/css/images/media/chapters.png differ diff --git a/dashboard-ui/css/images/media/fullscreen.png b/dashboard-ui/css/images/media/fullscreen.png new file mode 100644 index 0000000000..87ca5cabb5 Binary files /dev/null and b/dashboard-ui/css/images/media/fullscreen.png differ diff --git a/dashboard-ui/css/images/media/quality.png b/dashboard-ui/css/images/media/quality.png new file mode 100644 index 0000000000..b52641fcb6 Binary files /dev/null and b/dashboard-ui/css/images/media/quality.png differ diff --git a/dashboard-ui/css/images/media/reduce.png b/dashboard-ui/css/images/media/reduce.png new file mode 100644 index 0000000000..5b43ab3eed Binary files /dev/null and b/dashboard-ui/css/images/media/reduce.png differ diff --git a/dashboard-ui/css/jplayer.css b/dashboard-ui/css/jplayer.css new file mode 100644 index 0000000000..fc09a8b6b2 --- /dev/null +++ b/dashboard-ui/css/jplayer.css @@ -0,0 +1,545 @@ +/* + * Skin for jPlayer Plugin (jQuery JavaScript Library) + */ + +div.jp-audio, +div.jp-audio-stream, +div.jp-video { + + /* Edit the font-size to counteract inherited font sizing. + * Eg. 1.25em = 1 / 0.8em + */ + + font-size:1.25em; /* 1.25em for testing in site pages */ /* No parent CSS that can effect the size in the demos ZIP */ + font-family:Verdana, Arial, sans-serif; + line-height:1.6; + color: #FFF; + background-color:#4C4C4C; + position: absolute; + bottom: 0; +} +div.jp-audio { + width:420px; +} +div.jp-audio-stream { + width:182px; +} +div.jp-video-270p { + width:480px; +} +div.jp-video-360p { + width:640px; +} +div.jp-video-full { + /* Rules for IE6 (full-screen) */ + width:480px; + height:270px; + /* Rules for IE7 (full-screen) - Otherwise the relative container causes other page items that are not position:static (default) to appear over the video/gui. */ + position:static !important; position:relative +} + +/* The z-index rule is defined in this manner to enable Popcorn plugins that add overlays to video area. EG. Subtitles. */ +div.jp-video-full div div { + z-index:1000; +} + +div.jp-video-full div.jp-jplayer { + top: 0; + left: 0; + position: fixed !important; position: relative; /* Rules for IE6 (full-screen) */ + overflow: hidden; +} + +div.jp-video-full div.jp-gui { + position: fixed !important; position: static; /* Rules for IE6 (full-screen) */ + top: 0; + left: 0; + width:100%; + height:100%; + z-index:1001; /* 1 layer above the others. */ +} + +div.jp-video-full div.jp-interface { + position: absolute !important; position: relative; /* Rules for IE6 (full-screen) */ + bottom: 0; + left: 0; +} + +div.jp-interface { + position: relative; + background-color:#4C4C4C; + width:100%; +} + +div.jp-audio div.jp-type-single div.jp-interface { + height:80px; +} +div.jp-audio div.jp-type-playlist div.jp-interface { + height:80px; +} + +div.jp-audio-stream div.jp-type-single div.jp-interface { + height:80px; +} + +div.jp-video div.jp-interface { + border-top:1px solid #000; +} + +/* @group CONTROLS */ + +div.jp-controls-holder { + clear: both; + width:440px; + margin:0 auto; + position: relative; + overflow:hidden; +} + +div.jp-interface ul.jp-controls { + list-style-type:none; + margin:0; + padding: 0; + overflow:hidden; +} + +div.jp-audio ul.jp-controls { + width: 380px; + padding:20px 20px 0 20px; +} + +div.jp-audio-stream ul.jp-controls { + width: 142px; + padding:20px 20px 0 20px; +} + +div.jp-video div.jp-type-single ul.jp-controls { + width: 78px; + margin-left: 200px; +} + +div.jp-video div.jp-type-playlist ul.jp-controls { + width: 134px; + margin-left: 172px; +} +div.jp-video ul.jp-controls, +div.jp-interface ul.jp-controls li { + display:inline; + float: left; +} + +div.jp-interface ul.jp-controls a { + display:block; + overflow:hidden; + text-indent:-9999px; +} + +/* @end */ + +/* @group progress bar */ + +div.jp-progress { + overflow:hidden; + background-color: #ddd; +} +div.jp-audio div.jp-progress { + position: absolute; + top:32px; + height:15px; +} +div.jp-audio div.jp-type-single div.jp-progress { + left:110px; + width:186px; +} +div.jp-audio div.jp-type-playlist div.jp-progress { + left:166px; + width:130px; +} +div.jp-video div.jp-progress { + top:0px; + left:0px; + width:100%; + height:10px; +} +div.jp-seek-bar { + background: url("jplayer.blue.monday.jpg") 0 -202px repeat-x; + width:0px; + height:100%; + cursor: pointer; +} +div.jp-play-bar { + /*background: url("jplayer.blue.monday.jpg") 0 -218px repeat-x ;*/ + background: #D7742B; + width:0px; + height:100%; +} + +/* The seeking class is added/removed inside jPlayer */ +div.jp-seeking-bg { + background: url("jplayer.blue.monday.seeking.gif"); +} + +/* @end */ + +/* @group volume controls */ +div.jp_volume { + float: right; + width: 150px; + position: relative; + margin-top: -4px; + height: 30px; +} +div.jp_volume ul li { + list-style-type: none; +} +div.jp_volume ul li a { + text-indent: -9999px; +} + +a.jp-mute, +a.jp-unmute, +a.jp-volume-max { + width:18px; + height:15px; + margin-top:12px; +} + +div.jp-audio div.jp-type-single a.jp-mute, +div.jp-audio div.jp-type-single a.jp-unmute { + margin-left: 210px; +} +div.jp-audio div.jp-type-playlist a.jp-mute, +div.jp-audio div.jp-type-playlist a.jp-unmute { + margin-left: 154px; +} + +div.jp-audio-stream div.jp-type-single a.jp-mute, +div.jp-audio-stream div.jp-type-single a.jp-unmute { + margin-left:10px; +} + +div.jp-audio a.jp-volume-max, +div.jp-audio-stream a.jp-volume-max { + margin-left: 56px; +} + +div.jp-video a.jp-mute, +div.jp-video a.jp-unmute, +div.jp-video a.jp-volume-max { + position: absolute; + top:12px; + margin-top:0; +} + +div.jp-video a.jp-mute, +div.jp-video a.jp-unmute { + left: 50px; +} + +div.jp-video a.jp-volume-max { + left: 134px; +} + +a.jp-mute { + background: url("jplayer.blue.monday.jpg") 0 -170px no-repeat; +} +a.jp-mute:hover { + background: url("jplayer.blue.monday.jpg") -19px -170px no-repeat; +} +a.jp-unmute { + background: url("jplayer.blue.monday.jpg") -60px -170px no-repeat; + display: none; +} +a.jp-unmute:hover { + background: url("jplayer.blue.monday.jpg") -79px -170px no-repeat; +} +a.jp-volume-max { + background: url("jplayer.blue.monday.jpg") 0 -186px no-repeat; +} +a.jp-volume-max:hover { + background: url("jplayer.blue.monday.jpg") -19px -186px no-repeat; +} + +div.jp-volume-bar { + position: absolute; + overflow:hidden; + background: url("jplayer.blue.monday.jpg") 0 -250px repeat-x; + width:46px; + height:5px; + cursor: pointer; +} +div.jp-audio div.jp-volume-bar { + top:37px; + left:330px; +} +div.jp-audio-stream div.jp-volume-bar { + top:37px; + left:92px; +} +div.jp-video div.jp-volume-bar { + top:17px; + left:72px; +} +div.jp-volume-bar-value { + background: #D7742B; + width:0px; + height:5px; +} + +/* @end */ + +/* @group current time and duration */ + +div.jp-audio div.jp-time-holder { + position:absolute; + top:50px; +} +div.jp-audio div.jp-type-single div.jp-time-holder { + left:110px; + width:186px; +} +div.jp-audio div.jp-type-playlist div.jp-time-holder { + left:166px; + width:130px; +} + +div.jp-current-time, +div.jp-duration, div.jp_duration { + width:60px; + font-size:.64em; + font-style:oblique; +} +div.jp-current-time { + float: left; + display:inline; +} +div.jp-duration, div.jp_duration { + float: right; + display:inline; + text-align: right; +} + +div.jp-video div.jp-current-time { + margin-left:20px; +} +div.jp-video div.jp-duration, div.jp_duration { + margin-right:20px; +} + +/* @end */ + +/* @group playlist */ + +div.jp-title { + font-weight:bold; + text-align:center; +} + +div.jp-title, +div.jp-playlist { + width:100%; + background-color:#ccc; + border-top:1px solid #009be3; +} +div.jp-type-single div.jp-title, +div.jp-type-playlist div.jp-title, +div.jp-type-single div.jp-playlist { + border-top:none; +} +div.jp-title ul, +div.jp-playlist ul { + list-style-type:none; + margin:0; + padding:0 20px; + font-size:.72em; +} + +div.jp-title li { + padding:5px 0; + font-weight:bold; +} +div.jp-playlist li { + padding:5px 0 4px 20px; + border-bottom:1px solid #eee; +} + +div.jp-playlist li div { + display:inline; +} + +/* Note that the first-child (IE6) and last-child (IE6/7/8) selectors do not work on IE */ + +div.jp-type-playlist div.jp-playlist li:last-child { + padding:5px 0 5px 20px; + border-bottom:none; +} +div.jp-type-playlist div.jp-playlist li.jp-playlist-current { + list-style-type:square; + list-style-position:inside; + padding-left:7px; +} +div.jp-type-playlist div.jp-playlist a { + color: #333; + text-decoration: none; +} +div.jp-type-playlist div.jp-playlist a:hover { + color:#0d88c1; +} +div.jp-type-playlist div.jp-playlist a.jp-playlist-current { + color:#0d88c1; +} + +div.jp-type-playlist div.jp-playlist a.jp-playlist-item-remove { + float:right; + display:inline; + text-align:right; + margin-right:10px; + font-weight:bold; + color:#666; +} +div.jp-type-playlist div.jp-playlist a.jp-playlist-item-remove:hover { + color:#0d88c1; +} +div.jp-type-playlist div.jp-playlist span.jp-free-media { + float:right; + display:inline; + text-align:right; + margin-right:10px; +} +div.jp-type-playlist div.jp-playlist span.jp-free-media a{ + color:#666; +} +div.jp-type-playlist div.jp-playlist span.jp-free-media a:hover{ + color:#0d88c1; +} +span.jp-artist { + font-size:.8em; + color:#666; +} + +/* @end */ + +div.jp-jplayer audio, +div.jp-jplayer { + width:0px; + height:0px; +} + +div.jp-jplayer { + background-color: #000000; +} + +/* @group TOGGLES */ + +/* The audio toggles are nested inside jp-time-holder */ + +ul.jp-toggles { + list-style-type:none; + padding:0; + margin:0 auto; + overflow:hidden; +} + +div.jp-audio .jp-type-single ul.jp-toggles { + width:25px; +} +div.jp-audio .jp-type-playlist ul.jp-toggles { + width:55px; + margin: 0; + position: absolute; + left: 325px; + top: 50px; +} + +div.jp-video ul.jp-toggles { + margin-top:6px; + width: 40px; + float: right; +} + +ul.jp-toggles li { + display:block; + float:right; +} + +ul.jp-toggles li a { + display:block; + width:25px; + height:18px; + text-indent:-9999px; + line-height:100%; /* need this for IE6 */ +} + + +a.jp-repeat { + background: url("jplayer.blue.monday.jpg") 0 -290px no-repeat; +} + +a.jp-repeat:hover { + background: url("jplayer.blue.monday.jpg") -30px -290px no-repeat; +} + +a.jp-repeat-off { + background: url("jplayer.blue.monday.jpg") -60px -290px no-repeat; +} + +a.jp-repeat-off:hover { + background: url("jplayer.blue.monday.jpg") -90px -290px no-repeat; +} + +a.jp-shuffle { + background: url("jplayer.blue.monday.jpg") 0 -270px no-repeat; + margin-left: 5px; +} + +a.jp-shuffle:hover { + background: url("jplayer.blue.monday.jpg") -30px -270px no-repeat; +} + +a.jp-shuffle-off { + background: url("jplayer.blue.monday.jpg") -60px -270px no-repeat; + margin-left: 5px; +} + +a.jp-shuffle-off:hover { + background: url("jplayer.blue.monday.jpg") -90px -270px no-repeat; +} + + +/* @end */ + +/* @group NO SOLUTION error feedback */ + +.jp-no-solution { + padding:5px; + font-size:.8em; + background-color:#eee; + border:2px solid #009be3; + color:#000; + display:none; +} + +.jp-no-solution a { + color:#000; +} + +.jp-no-solution span { + font-size:1em; + display:block; + text-align:center; + font-weight:bold; +} + +/* @end */ + +/* Chapters and Quality Selector */ +.jp_res_chapters { + float: right; +} +.jp_res_chapters button { + margin-right: 2px; +} + +.jp_chapters, .jp_quality { + display: none; +} diff --git a/dashboard-ui/scripts/extensions.js b/dashboard-ui/scripts/extensions.js index 6d95dda5db..71fbcc88ed 100644 --- a/dashboard-ui/scripts/extensions.js +++ b/dashboard-ui/scripts/extensions.js @@ -331,388 +331,6 @@ function parseISO8601Date(s, toLocal) { })(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'] = new Array(1500000, 128000, 1920, 1080); - resolutions['medium'] = new Array(750000, 128000, 1280, 720); - resolutions['low'] = new Array(200000, 128000, 720, 480); - - 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.player.tag.src; - var src = parse_src_url(currentSrc); - var newSrc = "/mediabrowser/"+src.Type+"/"+src.item_id+"/stream."+src.stream+"?audioChannels="+src.audioChannels+"&audioBitrate="+resolutions[this.options.src[0].res][1]+ - "&videoBitrate="+resolutions[this.options.src[0].res][0]+"&maxWidth="+resolutions[this.options.src[0].res][2]+"&maxHeight="+resolutions[this.options.src[0].res][3]+ - "&videoCodec="+src.videoCodec+"&audioCodec="+src.audioCodec; - - if (this.player.duration() == "Infinity") { - if (currentSrc.indexOf("StartTimeTicks") >= 0) { - var startTimeTicks = currentSrc.match(new RegExp("StartTimeTicks=[0-9]+","g")); - var start_time = startTimeTicks[0].replace("StartTimeTicks=",""); - - newSrc += "&StartTimeTicks="+Math.floor(parseInt(start_time)+(10000000*current_time)); - }else { - newSrc += "&StartTimeTicks="+Math.floor(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.player.tag.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() { - } -}); - -/* - JS for the stop button in video.js player - */ - -/* - Define the base class for the stop button. - */ - -_V_.StopButton = _V_.Button.extend({ - - kind: "stop", - className: "vjs-stop-button", - - init: function(player, options) { - - this._super(player, options); - - }, - - buildCSSClass: function() { - - return this.className + " vjs-menu-button " + this._super(); - }, - - onClick: function() { - MediaPlayer.stop(); - } -}); - - //convert Ticks to human hr:min:sec format function ticks_to_human(str) { diff --git a/dashboard-ui/scripts/mediaplayer.js b/dashboard-ui/scripts/mediaplayer.js index 44ec08540c..2a2936b083 100644 --- a/dashboard-ui/scripts/mediaplayer.js +++ b/dashboard-ui/scripts/mediaplayer.js @@ -11,7 +11,7 @@ if (media.canPlayType) { - return media.canPlayType('video/mp4').replace(/no/, '') || media.canPlayType('video/mp2t').replace(/no/, '') || media.canPlayType('video/webm').replace(/no/, '') || media.canPlayType('application/x-mpegURL').replace(/no/, '') || media.canPlayType('video/ogv').replace(/no/, ''); + return media.canPlayType('video/mp4').replace(/no/, '') || media.canPlayType('video/webm').replace(/no/, '') || media.canPlayType('video/ogv').replace(/no/, ''); } return false; @@ -170,6 +170,9 @@ var volume = localStorage.getItem("volume") || 0.5; + //need to store current play position (reset to 0 on new video load) + MediaPlayer.playingTime = 0; + var baseParams = { audioChannels: 2, audioBitrate: 128000, @@ -183,102 +186,90 @@ baseParams['StartTimeTicks'] = startPosition; } - var html = ''; + var mp4VideoUrl = ApiClient.getUrl('Videos/' + item.Id + '/stream.mp4', $.extend({}, baseParams, { + videoCodec: 'h264', + audioCodec: 'aac' + })); + + var webmVideoUrl = ApiClient.getUrl('Videos/' + item.Id + '/stream.webm', $.extend({}, baseParams, { + videoCodec: 'vpx', + audioCodec: 'Vorbis' + })); + + var ogvVideoUrl = ApiClient.getUrl('Videos/' + item.Id + '/stream.ogv', $.extend({}, baseParams, { + videoCodec: 'theora', + audioCodec: 'Vorbis' + })); + + $("#media_player").jPlayer({ + ready: function () { + $(this).jPlayer("setMedia", { + m4v: mp4VideoUrl, + ogv: ogvVideoUrl, + webm: webmVideoUrl + }).jPlayer("play"); + + $('.jp_duration').html(ticks_to_human(item.RunTimeTicks)); + + $(this).bind($.jPlayer.event.timeupdate,function(event){ + MediaPlayer.playingTime = event.jPlayer.status.currentTime; + }); + + $(this).bind($.jPlayer.event.volumechange,function(event){ + localStorage.setItem("volume", event.jPlayer.options.volume ); + }); + + //add quality selector + var available_res = ['high','medium','low']; + $('.jp_quality').html(''); + $.each(available_res, function(i, value) { + var html = '
  • '+value+'
  • '; + $('.jp_quality').append(html); + }); + + $('.jp_chapters').html(''); + if (item.Chapters && item.Chapters.length) { + // Put together the available chapter list + $.each( item.Chapters, function( i, chapter ) { + var chapter_name = chapter.Name + " (" + ticks_to_human(chapter.StartPositionTicks) + ")"; + var html = '
  • '+chapter_name+'
  • '; + $('.jp_chapters').append(html); + }); + } + + MediaPlayer.updateProgress(); + ApiClient.reportPlaybackStart(Dashboard.getCurrentUserId(), item.Id); + + }, + volume: volume, + supplied: "m4v, ogv, webm", + cssSelectorAncestor: "#media_container", + emulateHtml: true + }); var nowPlayingBar = $('#nowPlayingBar'); - $('#mediaElement', nowPlayingBar).html(html).show(); - - _V_("videoWindow", {'controls': true, 'autoplay': true, 'preload': 'auto'}, function(){ - - var mp4VideoUrl = ApiClient.getUrl('Videos/' + item.Id + '/stream.mp4', $.extend({}, baseParams, { - videoCodec: 'h264', - audioCodec: 'aac' - })); - - var tsVideoUrl = ApiClient.getUrl('Videos/' + item.Id + '/stream.ts', $.extend({}, baseParams, { - videoCodec: 'h264', - audioCodec: 'aac' - })); - - var webmVideoUrl = ApiClient.getUrl('Videos/' + item.Id + '/stream.webm', $.extend({}, baseParams, { - videoCodec: 'vpx', - audioCodec: 'Vorbis' - })); - - var hlsVideoUrl = ApiClient.getUrl('Videos/' + item.Id + '/stream.m3u8', $.extend({}, baseParams, { - videoCodec: 'h264', - audioCodec: 'aac' - })); - - var ogvVideoUrl = ApiClient.getUrl('Videos/' + item.Id + '/stream.ogv', $.extend({}, baseParams, { - videoCodec: 'theora', - audioCodec: 'Vorbis' - })); - - (this).src([{ type: "video/webm", src: webmVideoUrl }, - { type: "video/mp4", src: mp4VideoUrl }, - { type: "video/mp2t; codecs='h264, aac'", src: tsVideoUrl }, - { type: "application/x-mpegURL", src: hlsVideoUrl }, - { type: "video/ogg", src: ogvVideoUrl }] - ).volume(volume); - - videoJSextension.setup_video( $( '#videoWindow' ), item ); - - (this).addEvent("loadstart",function(){ - $(".vjs-remaining-time-display").hide(); - }); - - (this).addEvent("durationchange",function(){ - if ((this).duration() != "Infinity") - $(".vjs-remaining-time-display").show(); - }); - - (this).addEvent("volumechange",function(){ - localStorage.setItem("volume", (this).volume()); - }); - - (this).addEvent("play", MediaPlayer.updateProgress); - - ApiClient.reportPlaybackStart(Dashboard.getCurrentUserId(), item.Id); - }); + $('#mediaElement', nowPlayingBar).show(); return $('video', nowPlayingBar)[0]; }, stop: function () { - var elem = MediaPlayer.mediaElement; + var startTimeTicks = $("#media_player video").attr("src").match(new RegExp("StartTimeTicks=[0-9]+","g")); + var start_time = startTimeTicks[0].replace("StartTimeTicks=",""); - //check if it's a video using VideoJS - if ($(elem).hasClass("vjs-tech")) { - var player = _V_("videoWindow"); + var item_string = $("#media_player video").attr("src").match(new RegExp("Videos/[0-9a-z\-]+","g")); + var item_id = item_string[0].replace("Videos/",""); - var startTimeTicks = player.tag.src.match(new RegExp("StartTimeTicks=[0-9]+","g")); - var start_time = startTimeTicks[0].replace("StartTimeTicks=",""); + var current_time = MediaPlayer.playingTime; + var positionTicks = parseInt(start_time) + Math.floor(10000000*current_time); - var item_string = player.tag.src.match(new RegExp("Videos/[0-9a-z\-]+","g")); - var item_id = item_string[0].replace("Videos/",""); + ApiClient.reportPlaybackStopped(Dashboard.getCurrentUserId(), item_id, positionTicks); - var positionTicks = parseInt(start_time) + Math.floor(10000000*player.currentTime()); + clearTimeout(MediaPlayer.progressInterval); - ApiClient.reportPlaybackStopped(Dashboard.getCurrentUserId(), item_id, positionTicks); - - clearTimeout(progressInterval); - - if (player.techName == "html5") { - player.tag.src = ""; - player.tech.removeTriggers(); - player.load(); - } - //player.tech.destroy(); - player.destroy(); - }else { - elem.pause(); - elem.src = ""; - } - - $(elem).remove(); + $("#media_player").jPlayer("destroy"); $('#nowPlayingBar').hide(); @@ -290,89 +281,74 @@ }, updateProgress: function () { - progressInterval = setInterval(function(){ - var player = _V_("videoWindow"); + MediaPlayer.progressInterval = setInterval(function(){ + var current_time = MediaPlayer.playingTime; - var startTimeTicks = player.tag.src.match(new RegExp("StartTimeTicks=[0-9]+","g")); + var startTimeTicks = $("#media_player video").attr("src").match(new RegExp("StartTimeTicks=[0-9]+","g")); var start_time = startTimeTicks[0].replace("StartTimeTicks=",""); - var item_string = player.tag.src.match(new RegExp("Videos/[0-9a-z\-]+","g")); + var item_string = $("#media_player video").attr("src").match(new RegExp("Videos/[0-9a-z\-]+","g")); var item_id = item_string[0].replace("Videos/",""); - var positionTicks = parseInt(start_time) + Math.floor(10000000*player.currentTime()); + var positionTicks = parseInt(start_time) + Math.floor(10000000*current_time); ApiClient.reportPlaybackProgress(Dashboard.getCurrentUserId(), item_id, positionTicks); },30000); + }, + + setResolution: function (new_res) { + var resolutions = new Array(); + resolutions['high'] = new Array(1500000, 128000, 1920, 1080); + resolutions['medium'] = new Array(750000, 128000, 1280, 720); + resolutions['low'] = new Array(200000, 128000, 720, 480); + + var current_time = MediaPlayer.playingTime; + + // Set the button text to the newly chosen quality + + + // Change the source and make sure we don't start the video over + var currentSrc = $("#media_player video").attr("src"); + var src = parse_src_url(currentSrc); + var newSrc = "/mediabrowser/"+src.Type+"/"+src.item_id+"/stream."+src.stream+"?audioChannels="+src.audioChannels+"&audioBitrate="+resolutions[new_res][1]+ + "&videoBitrate="+resolutions[new_res][0]+"&maxWidth="+resolutions[new_res][2]+"&maxHeight="+resolutions[new_res][3]+ + "&videoCodec="+src.videoCodec+"&audioCodec="+src.audioCodec; + + if (currentSrc.indexOf("StartTimeTicks") >= 0) { + var startTimeTicks = currentSrc.match(new RegExp("StartTimeTicks=[0-9]+","g")); + var start_time = startTimeTicks[0].replace("StartTimeTicks=",""); + + newSrc += "&StartTimeTicks="+Math.floor(parseInt(start_time)+(10000000*current_time)); + }else { + newSrc += "&StartTimeTicks="+Math.floor(10000000*current_time); + } + + //need to store current play position (reset to 0 on new video load) + MediaPlayer.playingTime = 0; + + $("#media_player").jPlayer("setMedia",{ + m4v: newSrc, + ogv: newSrc, + webm: newSrc + }).jPlayer("play"); + + }, + + setChapter: function (chapter_id, new_time) { + + var currentSrc = $("#media_player video").attr("src"); + + if (currentSrc.indexOf("StartTimeTicks") >= 0) { + var newSrc = currentSrc.replace(new RegExp("StartTimeTicks=[0-9]+","g"),"StartTimeTicks="+new_time); + }else { + var newSrc = currentSrc += "&StartTimeTicks="+new_time; + } + + $("#media_player").jPlayer("setMedia",{ + m4v: newSrc, + ogv: newSrc, + webm: newSrc + }).jPlayer("play"); } }; - -var videoJSextension = { - - /* - Add our video quality selector button to the videojs controls. This takes - a mandatory jQuery object of the