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