From c30b049fd0a8d8a5a704d6cd47c2c02e4aa7cddd Mon Sep 17 00:00:00 2001 From: Sterling Hirsh Date: Mon, 25 Mar 2013 11:23:04 -0400 Subject: [PATCH 001/187] Automatically destroy removed instances With multiple instances of jplayer, if one got removed from the dom without being destroyed, the others would cause a javascript error on play. This was caused by keeping a list of instances ($.jPlayer.prototype.instances) that would get out of sync with the page. With this patch, jPlayer clears the list of any nonexistent instances before iterating over the list. The error was triggered from pauseOthers() and $.jPlayer.pause(). --- jquery.jplayer/jquery.jplayer.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/jquery.jplayer/jquery.jplayer.js b/jquery.jplayer/jquery.jplayer.js index 3088d02c..1e10d168 100644 --- a/jquery.jplayer/jquery.jplayer.js +++ b/jquery.jplayer/jquery.jplayer.js @@ -160,6 +160,7 @@ ]; $.jPlayer.pause = function() { + $.jPlayer.prototype.destroyRemoved(); $.each($.jPlayer.prototype.instances, function(i, element) { if(element.data("jPlayer").status.srcSet) { // Check that media is set otherwise would cause error event. element.jPlayer("pause"); @@ -1113,6 +1114,17 @@ delete this.instances[this.internal.instance]; // Clear the instance on the static instance object }, + destroyRemoved: function() { // Destroy any instances that have gone away. + var self = this; + $.each(this.instances, function(i, element) { + if(self.element !== element) { // Do not destroy this instance. + if(!element.data("jPlayer")) { // Check that element is a real jPlayer. + element.jPlayer("destroy"); + delete self.instances[i]; + } + } + }); + }, enable: function() { // Plan to implement // options.disabled = false }, @@ -1724,6 +1736,7 @@ }, pauseOthers: function() { var self = this; + $.jPlayer.prototype.destroyRemoved(); $.each(this.instances, function(i, element) { if(self.element !== element) { // Do not this instance. if(element.data("jPlayer").status.srcSet) { // Check that media is set otherwise would cause error event. From dd68b8d700f856d867e556e6c671226667d786f2 Mon Sep 17 00:00:00 2001 From: Simon Owen Design Date: Mon, 23 Dec 2013 12:43:55 +0000 Subject: [PATCH 002/187] Add bower install instructions --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index a1b30116..e674e58b 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,10 @@ Support for [Zepto](http://zeptojs.com/) 1.0+ compiled with the data module. _(*) Optional counterpart formats to increase HTML5 cross-browser support._ +## Bower Install +* simple install using `bower install jplayer` +* see for more information. + ## License [jPlayer](http://jplayer.org/) is licensed under the [MIT license](http://opensource.org/licenses/MIT). From 985b8d700fba7672074c78ebb05b7cc7786d8ffe Mon Sep 17 00:00:00 2001 From: Happyworm Date: Mon, 30 Dec 2013 15:10:49 +0000 Subject: [PATCH 003/187] Bug fixed the console feature checking. --- jquery.jplayer/jquery.jplayer.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/jquery.jplayer/jquery.jplayer.js b/jquery.jplayer/jquery.jplayer.js index 69dd8d89..ed01bfa2 100644 --- a/jquery.jplayer/jquery.jplayer.js +++ b/jquery.jplayer/jquery.jplayer.js @@ -7,8 +7,8 @@ * http://opensource.org/licenses/MIT * * Author: Mark J Panaghiston - * Version: 2.5.2 - * Date: 15th December 2013 + * Version: 2.5.3 + * Date: 30th December 2013 */ /* Code verified using http://www.jshint.com/ */ @@ -470,7 +470,7 @@ $.jPlayer.prototype = { count: 0, // Static Variable: Change it via prototype. version: { // Static Object - script: "2.5.2", + script: "2.5.3", needFlash: "2.5.2", flash: "unknown" }, @@ -2899,8 +2899,8 @@ var msg = "jPlayer " + this.version.script + " : id='" + this.internal.self.id +"' : " + message; if(!this.options.consoleAlerts) { alert(msg); - } else if(console && console.log) { - console.log(msg); + } else if(window.console && window.console.log) { + window.console.log(msg); } }, _emulateHtmlBridge: function() { From f06e8f730e1d957df500164d407e7c8a12ce880c Mon Sep 17 00:00:00 2001 From: Happyworm Date: Mon, 30 Dec 2013 15:13:29 +0000 Subject: [PATCH 004/187] Updated the bloody JSON versions to 2.5.3 --- bower.json | 2 +- jplayer.jquery.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bower.json b/bower.json index a646ef49..f001c79f 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "jPlayer", - "version": "2.5.2", + "version": "2.5.3", "main": [ "./jquery.jplayer/jquery.jplayer.js", "./skin/pink.flag/jplayer.pink.flag.css" diff --git a/jplayer.jquery.json b/jplayer.jquery.json index e8b327ef..d5480437 100644 --- a/jplayer.jquery.json +++ b/jplayer.jquery.json @@ -11,7 +11,7 @@ "html5", "streaming" ], - "version": "2.5.2", + "version": "2.5.3", "author": { "name": "Mark J Panaghiston", "email": "markp@happyworm.com", diff --git a/package.json b/package.json index eeb3b917..41c133f6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jplayer", - "version": "2.5.2", + "version": "2.5.3", "description": "The jQuery HTML5 Audio / Video Library", "homepage": "http://www.jplayer.org/", "keywords": [ From 26e245584b69103043ce8cbd4eb7f702037df565 Mon Sep 17 00:00:00 2001 From: Happyworm Date: Thu, 9 Jan 2014 12:59:26 +0000 Subject: [PATCH 005/187] Fix: setMedia media URLs must be truethy before abs url. --- bower.json | 2 +- jplayer.jquery.json | 2 +- jquery.jplayer/jquery.jplayer.js | 10 +++++----- package.json | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/bower.json b/bower.json index f001c79f..22735e8d 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "jPlayer", - "version": "2.5.3", + "version": "2.5.4", "main": [ "./jquery.jplayer/jquery.jplayer.js", "./skin/pink.flag/jplayer.pink.flag.css" diff --git a/jplayer.jquery.json b/jplayer.jquery.json index d5480437..5db67082 100644 --- a/jplayer.jquery.json +++ b/jplayer.jquery.json @@ -11,7 +11,7 @@ "html5", "streaming" ], - "version": "2.5.3", + "version": "2.5.4", "author": { "name": "Mark J Panaghiston", "email": "markp@happyworm.com", diff --git a/jquery.jplayer/jquery.jplayer.js b/jquery.jplayer/jquery.jplayer.js index ed01bfa2..68440d3d 100644 --- a/jquery.jplayer/jquery.jplayer.js +++ b/jquery.jplayer/jquery.jplayer.js @@ -2,13 +2,13 @@ * jPlayer Plugin for jQuery JavaScript Library * http://www.jplayer.org * - * Copyright (c) 2009 - 2013 Happyworm Ltd + * Copyright (c) 2009 - 2014 Happyworm Ltd * Licensed under the MIT license. * http://opensource.org/licenses/MIT * * Author: Mark J Panaghiston - * Version: 2.5.3 - * Date: 30th December 2013 + * Version: 2.5.4 + * Date: 9th January 2014 */ /* Code verified using http://www.jshint.com/ */ @@ -470,7 +470,7 @@ $.jPlayer.prototype = { count: 0, // Static Variable: Change it via prototype. version: { // Static Object - script: "2.5.3", + script: "2.5.4", needFlash: "2.5.2", flash: "unknown" }, @@ -1669,7 +1669,7 @@ _absoluteMediaUrls: function(media) { var self = this; $.each(media, function(type, url) { - if(self.format[type]) { + if(url && self.format[type]) { media[type] = self._qualifyURL(url); } }); diff --git a/package.json b/package.json index 41c133f6..eba875c2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jplayer", - "version": "2.5.3", + "version": "2.5.4", "description": "The jQuery HTML5 Audio / Video Library", "homepage": "http://www.jplayer.org/", "keywords": [ From 3c9abf5527dc0f8f5e6179e79c4d51155afc107c Mon Sep 17 00:00:00 2001 From: Mark Panaghiston Date: Sat, 8 Mar 2014 17:03:32 +0000 Subject: [PATCH 006/187] Fix: For Android when changing audio and immediately playing. --- bower.json | 2 +- jplayer.jquery.json | 2 +- jquery.jplayer/jquery.jplayer.js | 58 ++++++++++++++++++++++++++++---- package.json | 2 +- 4 files changed, 55 insertions(+), 9 deletions(-) diff --git a/bower.json b/bower.json index 22735e8d..e1ca2e87 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "jPlayer", - "version": "2.5.4", + "version": "2.5.5", "main": [ "./jquery.jplayer/jquery.jplayer.js", "./skin/pink.flag/jplayer.pink.flag.css" diff --git a/jplayer.jquery.json b/jplayer.jquery.json index 5db67082..c7bf3d90 100644 --- a/jplayer.jquery.json +++ b/jplayer.jquery.json @@ -11,7 +11,7 @@ "html5", "streaming" ], - "version": "2.5.4", + "version": "2.5.5", "author": { "name": "Mark J Panaghiston", "email": "markp@happyworm.com", diff --git a/jquery.jplayer/jquery.jplayer.js b/jquery.jplayer/jquery.jplayer.js index 68440d3d..876a9da6 100644 --- a/jquery.jplayer/jquery.jplayer.js +++ b/jquery.jplayer/jquery.jplayer.js @@ -7,8 +7,8 @@ * http://opensource.org/licenses/MIT * * Author: Mark J Panaghiston - * Version: 2.5.4 - * Date: 9th January 2014 + * Version: 2.5.5 + * Date: 8th March 2014 */ /* Code verified using http://www.jshint.com/ */ @@ -470,7 +470,7 @@ $.jPlayer.prototype = { count: 0, // Static Variable: Change it via prototype. version: { // Static Object - script: "2.5.4", + script: "2.5.5", needFlash: "2.5.2", flash: "unknown" }, @@ -794,6 +794,17 @@ $.jPlayer.focus = this; } + // A fix for Android where older (2.3) and even some 4.x devices fail to work when changing the *audio* SRC and then playing immediately. + this.androidFix = { + setMedia: false, // True when media set + play: false, // True when a progress event will instruct the media to play + pause: false, // True when a progress event will instruct the media to pause at a time. + time: NaN // The play(time) parameter + }; + if($.jPlayer.platform.android) { + this.options.preload = this.options.preload !== 'auto' ? 'metadata' : 'auto'; // Default to metadata, but allow auto. + } + this.formats = []; // Array based on supplied string option. Order defines priority. this.solutions = []; // Array based on solution string option. Order defines priority. this.require = {}; // Which media types are required: video, audio. @@ -1261,6 +1272,15 @@ if(self.internal.cmdsIgnored && this.readyState > 0) { // Detect iOS executed the command self.internal.cmdsIgnored = false; } + self.androidFix.setMedia = false; // Disable the fix after the first progress event. + if(self.androidFix.play) { // Play Android audio - performing the fix. + self.androidFix.play = false; + self.play(self.androidFix.time); + } + if(self.androidFix.pause) { // Pause Android audio at time - performing the fix. + self.androidFix.pause = false; + self.pause(self.androidFix.time); + } self._getHtmlStatus(mediaElement); self._updateInterface(); self._trigger($.jPlayer.event.progress); @@ -1691,6 +1711,11 @@ this._resetGate(); this._resetActive(); + // Clear the Android Fix. + this.androidFix.setMedia = false; + this.androidFix.play = false; + this.androidFix.pause = false; + // Convert all media URLs to absolute URLs. media = this._absoluteMediaUrls(media); @@ -1719,6 +1744,11 @@ self.html.audio.gate = true; self._html_setAudio(media); self.html.active = true; + + // Setup the Android Fix - Only for HTML audio. + if($.jPlayer.platform.android) { + self.androidFix.setMedia = true; + } } else { self.flash.gate = true; self._flash_setAudio(media); @@ -2590,9 +2620,16 @@ var self = this, media = this.htmlElement.media; + this.androidFix.pause = false; // Cancel the pause fix. + this._html_load(); // Loads if required and clears any delayed commands. - if(!isNaN(time)) { + // Setup the Android Fix. + if(this.androidFix.setMedia) { + this.androidFix.play = true; + this.androidFix.time = time; + + } else if(!isNaN(time)) { // Attempt to play it, since iOS has been ignoring commands if(this.internal.cmdsIgnored) { @@ -2622,7 +2659,9 @@ _html_pause: function(time) { var self = this, media = this.htmlElement.media; - + + this.androidFix.play = false; // Cancel the play fix. + if(time > 0) { // We do not want the stop() command, which does pause(0), causing a load operation. this._html_load(); // Loads if required and clears any delayed commands. } else { @@ -2632,7 +2671,12 @@ // Order of these commands is important for Safari (Win) and IE9. Pause then change currentTime. media.pause(); - if(!isNaN(time)) { + // Setup the Android Fix. + if(this.androidFix.setMedia) { + this.androidFix.pause = true; + this.androidFix.time = time; + + } else if(!isNaN(time)) { try { if(!media.seekable || typeof media.seekable === "object" && media.seekable.length > 0) { media.currentTime = time; @@ -2656,6 +2700,8 @@ this._html_load(); // Loads if required and clears any delayed commands. + // This playHead() method needs a refactor to apply the android fix. + try { if(typeof media.seekable === "object" && media.seekable.length > 0) { media.currentTime = percent * media.seekable.end(media.seekable.length-1) / 100; diff --git a/package.json b/package.json index eba875c2..91cba22c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jplayer", - "version": "2.5.4", + "version": "2.5.5", "description": "The jQuery HTML5 Audio / Video Library", "homepage": "http://www.jplayer.org/", "keywords": [ From 07b4cca0f83d664da9b8e7c303241a4de91d9725 Mon Sep 17 00:00:00 2001 From: Mark Panaghiston Date: Tue, 1 Apr 2014 02:42:06 +0100 Subject: [PATCH 007/187] The media.title property of the setMedia object is now written to the jp-title class of the instance. --- jquery.jplayer/jquery.jplayer.js | 12 ++++++++++++ skin/blue.monday/jplayer.blue.monday.css | 21 ++++++++++----------- skin/pink.flag/jplayer.pink.flag.css | 17 ++++++++--------- 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/jquery.jplayer/jquery.jplayer.js b/jquery.jplayer/jquery.jplayer.js index 876a9da6..c0f62923 100644 --- a/jquery.jplayer/jquery.jplayer.js +++ b/jquery.jplayer/jquery.jplayer.js @@ -504,6 +504,7 @@ playbackRateBarValue: ".jp-playback-rate-bar-value", currentTime: ".jp-current-time", duration: ".jp-duration", + title: ".jp-title", fullScreen: ".jp-full-screen", // * restoreScreen: ".jp-restore-screen", // * repeat: ".jp-repeat", @@ -1782,6 +1783,17 @@ } } } + if(this.css.jq.title.length) { + if(typeof media.title === 'string') { + this.css.jq.title.html(media.title); + if(this.htmlElement.audio) { + this.htmlElement.audio.setAttribute('title', media.title); + } + if(this.htmlElement.video) { + this.htmlElement.video.setAttribute('title', media.title); + } + } + } this.status.srcSet = true; this.status.media = $.extend({}, media); this._updateButtons(false); diff --git a/skin/blue.monday/jplayer.blue.monday.css b/skin/blue.monday/jplayer.blue.monday.css index fa07bc51..ebe6ffb5 100644 --- a/skin/blue.monday/jplayer.blue.monday.css +++ b/skin/blue.monday/jplayer.blue.monday.css @@ -4,14 +4,13 @@ * * Skin Name: Blue Monday * - * Copyright (c) 2010-2012 Happyworm Ltd - * Dual licensed under the MIT and GPL licenses. + * Copyright (c) 2010-2014 Happyworm Ltd + * Licensed under the MIT license. * - http://www.opensource.org/licenses/mit-license.php - * - http://www.gnu.org/copyleft/gpl.html * * Author: Silvia Benvenuti - * Skin Version: 4.3 (jPlayer 2.2.0) - * Date: 19th November 2012 + * Skin Version: 4.4 (jPlayer 2.6.0) + * Date: 1st April 2014 */ div.jp-audio, @@ -378,23 +377,23 @@ div.jp-video div.jp-duration { /* @group playlist */ -div.jp-title { +div.jp-details { font-weight:bold; text-align:center; } -div.jp-title, +div.jp-details, 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-details, +div.jp-type-playlist div.jp-details, div.jp-type-single div.jp-playlist { border-top:none; } -div.jp-title ul, +div.jp-details ul, div.jp-playlist ul { list-style-type:none; margin:0; @@ -402,7 +401,7 @@ div.jp-playlist ul { font-size:.72em; } -div.jp-title li { +div.jp-details li { padding:5px 0; font-weight:bold; } diff --git a/skin/pink.flag/jplayer.pink.flag.css b/skin/pink.flag/jplayer.pink.flag.css index bafe3ff8..64cfd2a4 100644 --- a/skin/pink.flag/jplayer.pink.flag.css +++ b/skin/pink.flag/jplayer.pink.flag.css @@ -4,14 +4,13 @@ * * Skin Name: Pink Flag * - * Copyright (c) 2012 Happyworm Ltd - * Dual licensed under the MIT and GPL licenses. + * Copyright (c) 2012 - 2014 Happyworm Ltd + * Licensed under the MIT license. * - http://www.opensource.org/licenses/mit-license.php - * - http://www.gnu.org/copyleft/gpl.html * * Author: Silvia Benvenuti - * Skin Version: 1.2 (jPlayer 2.2.0) - * Date: 22nd October 2012 + * Skin Version: 1.3 (jPlayer 2.6.0) + * Date: 1st April 2014 */ div.jp-audio, @@ -499,7 +498,7 @@ div.jp-video .jp-volume-bar { /* @group playlist */ -.jp-title ul, +.jp-details ul, .jp-playlist ul { list-style-type:none; font-size:.7em; @@ -507,7 +506,7 @@ div.jp-video .jp-volume-bar { padding: 0; } -.jp-video .jp-title ul { +.jp-video .jp-details ul { margin: 0 20px 10px; } @@ -515,7 +514,7 @@ div.jp-video .jp-volume-bar { margin: 0 20px; } -.jp-title li, +.jp-details li, .jp-playlist li { position: relative; padding: 2px 0; @@ -524,7 +523,7 @@ div.jp-video .jp-volume-bar { overflow: hidden; } -.jp-title li{ +.jp-details li{ border-bottom:none; border-top:none; padding:0; From ce54455da533399bbae303872d7bbf435f0c02bc Mon Sep 17 00:00:00 2001 From: Mark Panaghiston Date: Tue, 1 Apr 2014 07:57:18 +0100 Subject: [PATCH 008/187] Fixing pink flag css CRLF. pt1 --- skin/pink.flag/jplayer.pink.flag.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skin/pink.flag/jplayer.pink.flag.css b/skin/pink.flag/jplayer.pink.flag.css index 64cfd2a4..9396d4cb 100644 --- a/skin/pink.flag/jplayer.pink.flag.css +++ b/skin/pink.flag/jplayer.pink.flag.css @@ -9,7 +9,7 @@ * - http://www.opensource.org/licenses/mit-license.php * * Author: Silvia Benvenuti - * Skin Version: 1.3 (jPlayer 2.6.0) + * Skin Version: 1.3 (jPlayer 2.6.0) - force * Date: 1st April 2014 */ From 3461207e993acf78414cec2d3b82577eee09000f Mon Sep 17 00:00:00 2001 From: Mark Panaghiston Date: Tue, 1 Apr 2014 07:57:34 +0100 Subject: [PATCH 009/187] Fixing pink flag css CRLF. pt2 --- skin/pink.flag/jplayer.pink.flag.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skin/pink.flag/jplayer.pink.flag.css b/skin/pink.flag/jplayer.pink.flag.css index 9396d4cb..64cfd2a4 100644 --- a/skin/pink.flag/jplayer.pink.flag.css +++ b/skin/pink.flag/jplayer.pink.flag.css @@ -9,7 +9,7 @@ * - http://www.opensource.org/licenses/mit-license.php * * Author: Silvia Benvenuti - * Skin Version: 1.3 (jPlayer 2.6.0) - force + * Skin Version: 1.3 (jPlayer 2.6.0) * Date: 1st April 2014 */ From 29c7d3b636a4025d0bd905d17966236bda67d982 Mon Sep 17 00:00:00 2001 From: Mark Panaghiston Date: Tue, 1 Apr 2014 09:08:40 +0100 Subject: [PATCH 010/187] Added the remainingDuration and toggleDuration options and implemented the features. --- jquery.jplayer/jquery.jplayer.js | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/jquery.jplayer/jquery.jplayer.js b/jquery.jplayer/jquery.jplayer.js index c0f62923..91b7e21f 100644 --- a/jquery.jplayer/jquery.jplayer.js +++ b/jquery.jplayer/jquery.jplayer.js @@ -481,6 +481,8 @@ preload: 'metadata', // HTML5 Spec values: none, metadata, auto. volume: 0.8, // The volume. Number 0 to 1. muted: false, + remainingDuration: false, // When true, the remaining time is shown in the duration GUI element. + toggleDuration: false, // When true, clicks on the duration toggle between the duration and remaining display. playbackRate: 1, defaultPlaybackRate: 1, minPlaybackRate: 0.5, @@ -656,6 +658,7 @@ currentPercentAbsolute: 0, currentTime: 0, duration: 0, + remaining: 0, videoWidth: 0, // Intrinsic width of the video in pixels. videoHeight: 0, // Intrinsic height of the video in pixels. readyState: 0, @@ -1443,6 +1446,8 @@ this.status.currentPercentAbsolute = cpa; this.status.currentTime = ct; + this.status.remaining = this.status.duration - this.status.currentTime; + this.status.videoWidth = media.videoWidth; this.status.videoHeight = media.videoHeight; @@ -1592,6 +1597,7 @@ this.status.currentPercentAbsolute = status.currentPercentAbsolute; this.status.currentTime = status.currentTime; this.status.duration = status.duration; + this.status.remaining = status.duration - status.currentTime; this.status.videoWidth = status.videoWidth; this.status.videoHeight = status.videoHeight; @@ -1656,7 +1662,11 @@ this.css.jq.currentTime.text(this._convertTime(this.status.currentTime)); } if(this.css.jq.duration.length) { - this.css.jq.duration.text(this._convertTime(this.status.duration)); + if(this.options.remainingDuration) { + this.css.jq.duration.text((this.status.remaining > 0 ? '-' : '') + this._convertTime(this.status.remaining)); + } else { + this.css.jq.duration.text(this._convertTime(this.status.duration)); + } } }, _convertTime: ConvertTime.prototype.time, @@ -2134,6 +2144,11 @@ }); } }, + duration: function(e) { + if(this.options.toggleDuration) { + this._setOption("remainingDuration", !this.options.remainingDuration); + } + }, seekBar: function(e) { // Handles clicks on the seekBar if(this.css.jq.seekBar.length) { // Using $(e.currentTarget) to enable multiple seek bars @@ -2355,6 +2370,13 @@ case "loop" : this._loop(value); break; + case "remainingDuration" : + this.options[key] = value; + this._updateInterface(); + break; + case "toggleDuration" : + this.options[key] = value; + break; case "nativeVideoControls" : this.options[key] = $.extend({}, this.options[key], value); // store a merged copy of it, incase not all properties changed. this.status.nativeVideoControls = this._uaBlocklist(this.options.nativeVideoControls); From 2ae3fac1a007696a9db87dd35465edf9146c76ee Mon Sep 17 00:00:00 2001 From: Mark Panaghiston Date: Tue, 1 Apr 2014 09:27:47 +0100 Subject: [PATCH 011/187] Added a new setmedia event, when setMedia is used. --- add-on/jquery.jplayer.inspector.js | 11 ++++++----- jquery.jplayer/jquery.jplayer.js | 2 ++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/add-on/jquery.jplayer.inspector.js b/add-on/jquery.jplayer.inspector.js index 2962b44f..e7a6cf89 100644 --- a/add-on/jquery.jplayer.inspector.js +++ b/add-on/jquery.jplayer.inspector.js @@ -2,15 +2,15 @@ * jPlayerInspector Plugin for jPlayer Plugin for jQuery JavaScript Library * http://www.jplayer.org * - * Copyright (c) 2009 - 2013 Happyworm Ltd + * Copyright (c) 2009 - 2014 Happyworm Ltd * Licensed under the MIT license. * http://www.opensource.org/licenses/MIT * * Author: Mark J Panaghiston - * Version: 1.0.4 - * Date: 29th January 2013 + * Version: 1.0.5 + * Date: 1st April 2014 * - * For use with jPlayer Version: 2.2.19+ + * For use with jPlayer Version: 2.6.0+ * * Note: Declare inspector instances after jPlayer instances. ie., Otherwise the jPlayer instance is nonsense. */ @@ -70,17 +70,18 @@ // MJP: Doing it longhand so order and layout easier to control. structure += '
' + + '
' + '
' + '
' + '
' + '
' - + '
' + '
' + '
' + '
' + '
' + '
' + + '
' + '
' + '
' diff --git a/jquery.jplayer/jquery.jplayer.js b/jquery.jplayer/jquery.jplayer.js index 91b7e21f..7636db67 100644 --- a/jquery.jplayer/jquery.jplayer.js +++ b/jquery.jplayer/jquery.jplayer.js @@ -113,6 +113,7 @@ $.each( [ 'ready', + 'setmedia', // Fires when the media is set 'flashreset', // Similar to the ready event if the Flash solution is set to display:none and then shown again or if it's reloaded for another reason by the browser. For example, using CSS position:fixed on Firefox for the full screen feature. 'resize', // Occurs when the size changes through a full/restore screen operation or if the size/sizeFull options are changed. 'repeat', // Occurs when the repeat status changes. Usually through clicks on the repeat button of the interface. @@ -1808,6 +1809,7 @@ this.status.media = $.extend({}, media); this._updateButtons(false); this._updateInterface(); + this._trigger($.jPlayer.event.setmedia); } else { // jPlayer cannot support any formats provided in this browser // Send an error event this._error( { From 95fb73542f9f00a870e7fcb649f3afe827227864 Mon Sep 17 00:00:00 2001 From: Mark Panaghiston Date: Tue, 1 Apr 2014 09:57:41 +0100 Subject: [PATCH 012/187] The currentTime and duration only update the GUI when they change. --- jquery.jplayer/jquery.jplayer.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/jquery.jplayer/jquery.jplayer.js b/jquery.jplayer/jquery.jplayer.js index 7636db67..969d0eae 100644 --- a/jquery.jplayer/jquery.jplayer.js +++ b/jquery.jplayer/jquery.jplayer.js @@ -1659,14 +1659,22 @@ this.css.jq.playBar.width(this.status.currentPercentRelative+"%"); } } + var currentTimeText = ''; if(this.css.jq.currentTime.length) { - this.css.jq.currentTime.text(this._convertTime(this.status.currentTime)); + currentTimeText = this._convertTime(this.status.currentTime); + if(currentTimeText !== this.css.jq.currentTime.text()) { + this.css.jq.currentTime.text(this._convertTime(this.status.currentTime)); + } } + var durationText = ''; if(this.css.jq.duration.length) { if(this.options.remainingDuration) { - this.css.jq.duration.text((this.status.remaining > 0 ? '-' : '') + this._convertTime(this.status.remaining)); + durationText = (this.status.remaining > 0 ? '-' : '') + this._convertTime(this.status.remaining); } else { - this.css.jq.duration.text(this._convertTime(this.status.duration)); + durationText = this._convertTime(this.status.duration); + } + if(durationText !== this.css.jq.duration.text()) { + this.css.jq.duration.text(durationText); } } }, From 3c74f753b6a44013d0d3dd64727b7d1a567a67b0 Mon Sep 17 00:00:00 2001 From: Mark Panaghiston Date: Tue, 1 Apr 2014 10:29:05 +0100 Subject: [PATCH 013/187] The media.duration property of the setMedia object is now used when displaying the jp-duration. --- jquery.jplayer/jquery.jplayer.js | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/jquery.jplayer/jquery.jplayer.js b/jquery.jplayer/jquery.jplayer.js index 969d0eae..1af4b2e2 100644 --- a/jquery.jplayer/jquery.jplayer.js +++ b/jquery.jplayer/jquery.jplayer.js @@ -1666,12 +1666,22 @@ this.css.jq.currentTime.text(this._convertTime(this.status.currentTime)); } } - var durationText = ''; + var durationText = '', + duration = this.status.duration, + remaining = this.status.remaining; if(this.css.jq.duration.length) { - if(this.options.remainingDuration) { - durationText = (this.status.remaining > 0 ? '-' : '') + this._convertTime(this.status.remaining); + if(typeof this.status.media.duration === 'string') { + durationText = this.status.media.duration; } else { - durationText = this._convertTime(this.status.duration); + if(typeof this.status.media.duration === 'number') { + duration = this.status.media.duration; + remaining = duration - this.status.currentTime; + } + if(this.options.remainingDuration) { + durationText = (remaining > 0 ? '-' : '') + this._convertTime(remaining); + } else { + durationText = this._convertTime(duration); + } } if(durationText !== this.css.jq.duration.text()) { this.css.jq.duration.text(durationText); From 49dc5c4009b90d2809d13b32cdf991fc8a233bb5 Mon Sep 17 00:00:00 2001 From: Mark Panaghiston Date: Tue, 1 Apr 2014 14:16:16 +0100 Subject: [PATCH 014/187] Fix: Destroy now removes full screen event listeners. --- bower.json | 2 +- jplayer.jquery.json | 2 +- jquery.jplayer/jquery.jplayer.js | 8 ++++---- package.json | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/bower.json b/bower.json index e1ca2e87..bc6eb809 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "jPlayer", - "version": "2.5.5", + "version": "2.5.6", "main": [ "./jquery.jplayer/jquery.jplayer.js", "./skin/pink.flag/jplayer.pink.flag.css" diff --git a/jplayer.jquery.json b/jplayer.jquery.json index c7bf3d90..af6e1558 100644 --- a/jplayer.jquery.json +++ b/jplayer.jquery.json @@ -11,7 +11,7 @@ "html5", "streaming" ], - "version": "2.5.5", + "version": "2.5.6", "author": { "name": "Mark J Panaghiston", "email": "markp@happyworm.com", diff --git a/jquery.jplayer/jquery.jplayer.js b/jquery.jplayer/jquery.jplayer.js index 876a9da6..b91cb4d9 100644 --- a/jquery.jplayer/jquery.jplayer.js +++ b/jquery.jplayer/jquery.jplayer.js @@ -7,8 +7,8 @@ * http://opensource.org/licenses/MIT * * Author: Mark J Panaghiston - * Version: 2.5.5 - * Date: 8th March 2014 + * Version: 2.5.6 + * Date: 1st April 2014 */ /* Code verified using http://www.jshint.com/ */ @@ -470,7 +470,7 @@ $.jPlayer.prototype = { count: 0, // Static Variable: Change it via prototype. version: { // Static Object - script: "2.5.5", + script: "2.5.6", needFlash: "2.5.2", flash: "unknown" }, @@ -2504,7 +2504,7 @@ _fullscreenRemoveEventListeners: function() { var fs = $.jPlayer.nativeFeatures.fullscreen; if(this.internal.fullscreenchangeHandler) { - document.addEventListener(fs.event.fullscreenchange, this.internal.fullscreenchangeHandler, false); + document.removeEventListener(fs.event.fullscreenchange, this.internal.fullscreenchangeHandler, false); } }, _fullscreenchange: function() { diff --git a/package.json b/package.json index 91cba22c..c91805d7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jplayer", - "version": "2.5.5", + "version": "2.5.6", "description": "The jQuery HTML5 Audio / Video Library", "homepage": "http://www.jplayer.org/", "keywords": [ From 47ca9d9c7358f70f301180092dcc9ff8cabbb325 Mon Sep 17 00:00:00 2001 From: Mark Panaghiston Date: Wed, 2 Apr 2014 13:16:05 +0100 Subject: [PATCH 015/187] Updated Unchanged AS files for year. --- actionscript/happyworm/jPlayer/ConnectManager.as | 2 +- actionscript/happyworm/jPlayer/JplayerEvent.as | 2 +- actionscript/happyworm/jPlayer/JplayerMp3.as | 2 +- actionscript/happyworm/jPlayer/JplayerMp4.as | 2 +- actionscript/happyworm/jPlayer/JplayerRtmp.as | 2 +- actionscript/happyworm/jPlayer/TraceOut.as | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/actionscript/happyworm/jPlayer/ConnectManager.as b/actionscript/happyworm/jPlayer/ConnectManager.as index f8020544..ae16b7d0 100644 --- a/actionscript/happyworm/jPlayer/ConnectManager.as +++ b/actionscript/happyworm/jPlayer/ConnectManager.as @@ -2,7 +2,7 @@ * jPlayer Plugin for jQuery JavaScript Library * http://www.jplayer.org * - * Copyright (c) 2009 - 2013 Happyworm Ltd + * Copyright (c) 2009 - 2014 Happyworm Ltd * Licensed under the MIT license. * http://opensource.org/licenses/MIT * diff --git a/actionscript/happyworm/jPlayer/JplayerEvent.as b/actionscript/happyworm/jPlayer/JplayerEvent.as index 57eb696b..13b0971f 100644 --- a/actionscript/happyworm/jPlayer/JplayerEvent.as +++ b/actionscript/happyworm/jPlayer/JplayerEvent.as @@ -2,7 +2,7 @@ * jPlayer Plugin for jQuery JavaScript Library * http://www.jplayer.org * - * Copyright (c) 2009 - 2013 Happyworm Ltd + * Copyright (c) 2009 - 2014 Happyworm Ltd * Licensed under the MIT license. * http://opensource.org/licenses/MIT * diff --git a/actionscript/happyworm/jPlayer/JplayerMp3.as b/actionscript/happyworm/jPlayer/JplayerMp3.as index cf8f9c90..1e6cf162 100644 --- a/actionscript/happyworm/jPlayer/JplayerMp3.as +++ b/actionscript/happyworm/jPlayer/JplayerMp3.as @@ -2,7 +2,7 @@ * jPlayer Plugin for jQuery JavaScript Library * http://www.jplayer.org * - * Copyright (c) 2009 - 2013 Happyworm Ltd + * Copyright (c) 2009 - 2014 Happyworm Ltd * Licensed under the MIT license. * http://opensource.org/licenses/MIT * diff --git a/actionscript/happyworm/jPlayer/JplayerMp4.as b/actionscript/happyworm/jPlayer/JplayerMp4.as index c3fffd3c..63bcfb66 100644 --- a/actionscript/happyworm/jPlayer/JplayerMp4.as +++ b/actionscript/happyworm/jPlayer/JplayerMp4.as @@ -2,7 +2,7 @@ * jPlayer Plugin for jQuery JavaScript Library * http://www.jplayer.org * - * Copyright (c) 2009 - 2013 Happyworm Ltd + * Copyright (c) 2009 - 2014 Happyworm Ltd * Licensed under the MIT license. * http://opensource.org/licenses/MIT * diff --git a/actionscript/happyworm/jPlayer/JplayerRtmp.as b/actionscript/happyworm/jPlayer/JplayerRtmp.as index 5e9d65e8..1f8505aa 100644 --- a/actionscript/happyworm/jPlayer/JplayerRtmp.as +++ b/actionscript/happyworm/jPlayer/JplayerRtmp.as @@ -2,7 +2,7 @@ * jPlayer Plugin for jQuery JavaScript Library * http://www.jplayer.org * - * Copyright (c) 2009 - 2013 Happyworm Ltd + * Copyright (c) 2009 - 2014 Happyworm Ltd * Licensed under the MIT license. * http://opensource.org/licenses/MIT * diff --git a/actionscript/happyworm/jPlayer/TraceOut.as b/actionscript/happyworm/jPlayer/TraceOut.as index 956e3a20..a66cca5b 100644 --- a/actionscript/happyworm/jPlayer/TraceOut.as +++ b/actionscript/happyworm/jPlayer/TraceOut.as @@ -2,7 +2,7 @@ * jPlayer Plugin for jQuery JavaScript Library * http://www.jplayer.org * - * Copyright (c) 2009 - 2013 Happyworm Ltd + * Copyright (c) 2009 - 2014 Happyworm Ltd * Licensed under the MIT license. * http://opensource.org/licenses/MIT * From 3d3fe2d70345069f73da063ad9e2757587513796 Mon Sep 17 00:00:00 2001 From: Mark Panaghiston Date: Wed, 2 Apr 2014 13:28:37 +0100 Subject: [PATCH 016/187] Updated versioning to jPlayer 2.6.0 --- actionscript/Jplayer.as | 8 ++++---- .../happyworm/jPlayer/JplayerStatus.as | 6 +++--- bower.json | 2 +- jplayer.jquery.json | 2 +- jquery.jplayer/Jplayer.swf | Bin 14162 -> 14163 bytes jquery.jplayer/jquery.jplayer.js | 8 ++++---- package.json | 2 +- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/actionscript/Jplayer.as b/actionscript/Jplayer.as index 1a0bd7ef..f07b1296 100644 --- a/actionscript/Jplayer.as +++ b/actionscript/Jplayer.as @@ -2,13 +2,13 @@ * jPlayer Plugin for jQuery JavaScript Library * http://www.jplayer.org * - * Copyright (c) 2009 - 2013 Happyworm Ltd - * Licensed under the MIT. + * Copyright (c) 2009 - 2014 Happyworm Ltd + * Licensed under the MIT license. * http://opensource.org/licenses/MIT * * Author: Mark J Panaghiston - * Version: 2.5.2 - * Date: 15th December 2013 + * Version: 2.6.0 + * Date: 2nd April 2014 * * FlashVars expected: (AS3 property of: loaderInfo.parameters) * id: (URL Encoded: String) Id of jPlayer instance diff --git a/actionscript/happyworm/jPlayer/JplayerStatus.as b/actionscript/happyworm/jPlayer/JplayerStatus.as index c31f055e..909816a7 100644 --- a/actionscript/happyworm/jPlayer/JplayerStatus.as +++ b/actionscript/happyworm/jPlayer/JplayerStatus.as @@ -2,18 +2,18 @@ * jPlayer Plugin for jQuery JavaScript Library * http://www.jplayer.org * - * Copyright (c) 2009 - 2013 Happyworm Ltd + * Copyright (c) 2009 - 2014 Happyworm Ltd * Licensed under the MIT license. * http://opensource.org/licenses/MIT * * Author: Mark J Panaghiston - * Date: 15th Devember 2013 + * Date: 2nd April 2014 */ package happyworm.jPlayer { public class JplayerStatus { - public static const VERSION:String = "2.5.2"; // The version of the Flash jPlayer entity. + public static const VERSION:String = "2.6.0"; // The version of the Flash jPlayer entity. public var volume:Number = 0.5; // Not affected by reset() public var muted:Boolean = false; // Not affected by reset() diff --git a/bower.json b/bower.json index bc6eb809..f3454511 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "jPlayer", - "version": "2.5.6", + "version": "2.6.0", "main": [ "./jquery.jplayer/jquery.jplayer.js", "./skin/pink.flag/jplayer.pink.flag.css" diff --git a/jplayer.jquery.json b/jplayer.jquery.json index af6e1558..2657baac 100644 --- a/jplayer.jquery.json +++ b/jplayer.jquery.json @@ -11,7 +11,7 @@ "html5", "streaming" ], - "version": "2.5.6", + "version": "2.6.0", "author": { "name": "Mark J Panaghiston", "email": "markp@happyworm.com", diff --git a/jquery.jplayer/Jplayer.swf b/jquery.jplayer/Jplayer.swf index 9561b94c921468ff27ec43304284f42cb1686109..cf7a4f4bafee840725d860e1b374ecd7c9ab5582 100644 GIT binary patch literal 14163 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͌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 z Date: Wed, 2 Apr 2014 17:22:26 +0100 Subject: [PATCH 017/187] Updated popcorn player for 2.6.0 --- popcorn/player/popcorn.jplayer.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/popcorn/player/popcorn.jplayer.js b/popcorn/player/popcorn.jplayer.js index 0c916c77..154e1909 100644 --- a/popcorn/player/popcorn.jplayer.js +++ b/popcorn/player/popcorn.jplayer.js @@ -2,16 +2,16 @@ * jPlayer Player Plugin for Popcorn JavaScript Library * http://www.jplayer.org * - * Copyright (c) 2013 Happyworm Ltd + * Copyright (c) 2012 - 2014 Happyworm Ltd * Licensed under the MIT license. * http://opensource.org/licenses/MIT * * Author: Mark J Panaghiston - * Version: 1.1.2 - * Date: 7th November 2013 + * Version: 1.1.3 + * Date: 2nd April 2014 * * For Popcorn Version: 1.3 - * For jPlayer Version: 2.5.0 + * For jPlayer Version: 2.6.0 * Requires: jQuery 1.7+ * Note: jQuery dependancy cannot be removed since jPlayer 2 is a jQuery plugin. Use of jQuery will be kept to a minimum. */ @@ -22,9 +22,9 @@ (function(Popcorn) { - var JQUERY_SCRIPT = '//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js', // Used if jQuery not already present. - JPLAYER_SCRIPT = '//www.jplayer.org/2.5.0/js/jquery.jplayer.min.js', // Used if jPlayer not already present. - JPLAYER_SWFPATH = '//www.jplayer.org/2.5.0/js/Jplayer.swf', // Used if not specified in jPlayer options via SRC Object. + var JQUERY_SCRIPT = '//code.jquery.com/jquery-1.11.0.min.js', // Used if jQuery not already present. + JPLAYER_SCRIPT = '//www.jplayer.org/2.6.0/js/jquery.jplayer.min.js', // Used if jPlayer not already present. + JPLAYER_SWFPATH = '//www.jplayer.org/2.6.0/js/Jplayer.swf', // Used if not specified in jPlayer options via SRC Object. SOLUTION = 'html,flash', // The default solution option. DEBUG = false, // Decided to leave the debugging option and console output in for the time being. Overhead is trivial. jQueryDownloading = false, // Flag to stop multiple instances from each pulling in jQuery, thus corrupting it. From 7a2ebbb04185a3fe38bc6b47d6596467b79ab454 Mon Sep 17 00:00:00 2001 From: Mark Panaghiston Date: Wed, 2 Apr 2014 17:55:46 +0100 Subject: [PATCH 018/187] Corrected date in Flash menu --- actionscript/Jplayer.as | 2 +- jquery.jplayer/Jplayer.swf | Bin 14163 -> 14162 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/actionscript/Jplayer.as b/actionscript/Jplayer.as index f07b1296..c621aace 100644 --- a/actionscript/Jplayer.as +++ b/actionscript/Jplayer.as @@ -122,7 +122,7 @@ package { var myContextMenu:ContextMenu = new ContextMenu(); myContextMenu.hideBuiltInItems(); var menuItem_jPlayer:ContextMenuItem = new ContextMenuItem("jPlayer " + JplayerStatus.VERSION); - var menuItem_happyworm:ContextMenuItem = new ContextMenuItem("© 2009-2013 Happyworm Ltd", true); + var menuItem_happyworm:ContextMenuItem = new ContextMenuItem("© 2009-2014 Happyworm Ltd", true); menuItem_jPlayer.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, menuSelectHandler_jPlayer); menuItem_happyworm.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, menuSelectHandler_happyworm); myContextMenu.customItems.push(menuItem_jPlayer, menuItem_happyworm); diff --git a/jquery.jplayer/Jplayer.swf b/jquery.jplayer/Jplayer.swf index cf7a4f4bafee840725d860e1b374ecd7c9ab5582..3012248067e63a1b0a9da3d2185d271de1fb8188 100644 GIT binary patch literal 14162 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 Uf>`J>z8Cw{RlfqfFUgPp2hU0Sp8x;= literal 14163 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 Date: Thu, 3 Apr 2014 21:57:30 +0100 Subject: [PATCH 019/187] Fix: Added captureDuration option to capture clicks on the duration. --- jquery.jplayer/jquery.jplayer.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/jquery.jplayer/jquery.jplayer.js b/jquery.jplayer/jquery.jplayer.js index 2214019a..facb17a4 100644 --- a/jquery.jplayer/jquery.jplayer.js +++ b/jquery.jplayer/jquery.jplayer.js @@ -7,8 +7,8 @@ * http://opensource.org/licenses/MIT * * Author: Mark J Panaghiston - * Version: 2.6.0 - * Date: 2nd April 2014 + * Version: 2.6.1 + * Date: 3rd April 2014 */ /* Code verified using http://www.jshint.com/ */ @@ -471,7 +471,7 @@ $.jPlayer.prototype = { count: 0, // Static Variable: Change it via prototype. version: { // Static Object - script: "2.6.0", + script: "2.6.1", needFlash: "2.6.0", flash: "unknown" }, @@ -484,6 +484,7 @@ muted: false, remainingDuration: false, // When true, the remaining time is shown in the duration GUI element. toggleDuration: false, // When true, clicks on the duration toggle between the duration and remaining display. + captureDuration: true, // When true, clicks on the duration are captured and no longer propagate up the DOM. playbackRate: 1, defaultPlaybackRate: 1, minPlaybackRate: 0.5, @@ -2166,6 +2167,9 @@ }, duration: function(e) { if(this.options.toggleDuration) { + if(this.options.captureDuration) { + e.stopPropagation(); + } this._setOption("remainingDuration", !this.options.remainingDuration); } }, From aca237f4f6873c4fe952064636e8fd54926e0682 Mon Sep 17 00:00:00 2001 From: Mark Panaghiston Date: Thu, 3 Apr 2014 21:59:05 +0100 Subject: [PATCH 020/187] Updated the versioning --- bower.json | 2 +- jplayer.jquery.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bower.json b/bower.json index f3454511..44acc064 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "jPlayer", - "version": "2.6.0", + "version": "2.6.1", "main": [ "./jquery.jplayer/jquery.jplayer.js", "./skin/pink.flag/jplayer.pink.flag.css" diff --git a/jplayer.jquery.json b/jplayer.jquery.json index 2657baac..05f5f00b 100644 --- a/jplayer.jquery.json +++ b/jplayer.jquery.json @@ -11,7 +11,7 @@ "html5", "streaming" ], - "version": "2.6.0", + "version": "2.6.1", "author": { "name": "Mark J Panaghiston", "email": "markp@happyworm.com", diff --git a/package.json b/package.json index 59a70c36..de73ef5f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jplayer", - "version": "2.6.0", + "version": "2.6.1", "description": "The jQuery HTML5 Audio / Video Library", "homepage": "http://www.jplayer.org/", "keywords": [ From 0810db8c46d8362f24b368a5537523495502ef0f Mon Sep 17 00:00:00 2001 From: Matt Fawcett Date: Wed, 23 Apr 2014 13:09:39 +0100 Subject: [PATCH 021/187] Update the android_phone fullscreen support useragent detection regex so that it allows fullscreen on android phones using the Chrome browser. Chrome on android has supported full screen since it's release: http://www.brighthand.com/default.asp?newsID=20141 --- jquery.jplayer/jquery.jplayer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jquery.jplayer/jquery.jplayer.js b/jquery.jplayer/jquery.jplayer.js index facb17a4..9c365187 100644 --- a/jquery.jplayer/jquery.jplayer.js +++ b/jquery.jplayer/jquery.jplayer.js @@ -546,7 +546,7 @@ iphone: /iphone/, ipod: /ipod/, android_pad: /android [0-3]\.(?!.*?mobile)/, - android_phone: /android.*?mobile/, + android_phone: /(?=.*android)(?!.*chrome)(?=.*mobile)/, blackberry: /blackberry/, windows_ce: /windows ce/, iemobile: /iemobile/, From 16e8c38f031f43a703be8200407164c4d284c29c Mon Sep 17 00:00:00 2001 From: Matt Fawcett Date: Fri, 2 May 2014 09:04:39 +0100 Subject: [PATCH 022/187] Add support for native fullscreen api in Internet explorer. Currenly works in IE11 (the latest) using the ms prefix. --- jquery.jplayer/jquery.jplayer.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/jquery.jplayer/jquery.jplayer.js b/jquery.jplayer/jquery.jplayer.js index facb17a4..8f737fea 100644 --- a/jquery.jplayer/jquery.jplayer.js +++ b/jquery.jplayer/jquery.jplayer.js @@ -353,13 +353,22 @@ 'webkitExitFullscreen', '', '' + ], + ms: [ + '', + 'msFullscreenElement', + 'msRequestFullscreen', + 'msExitFullscreen', + 'MSFullscreenChange', + 'MSFullscreenError' ] }, specOrder = [ 'w3c', 'moz', 'webkit', - 'webkitVideo' + 'webkitVideo', + 'ms' ], fs, i, il; @@ -368,7 +377,8 @@ w3c: !!d[spec.w3c[0]], moz: !!d[spec.moz[0]], webkit: typeof d[spec.webkit[3]] === 'function', - webkitVideo: typeof v[spec.webkitVideo[2]] === 'function' + webkitVideo: typeof v[spec.webkitVideo[2]] === 'function', + ms: typeof v[spec.ms[2]] === 'function' }, used: {} }; From bd311f02050be5fb5456863be39974d47cf9ed49 Mon Sep 17 00:00:00 2001 From: Giorgio Consorti Date: Sun, 18 May 2014 19:34:32 +0200 Subject: [PATCH 023/187] fix for wrong mousemove event on Chrome browser --- jquery.jplayer/jquery.jplayer.js | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/jquery.jplayer/jquery.jplayer.js b/jquery.jplayer/jquery.jplayer.js index facb17a4..17fbed7a 100644 --- a/jquery.jplayer/jquery.jplayer.js +++ b/jquery.jplayer/jquery.jplayer.js @@ -685,6 +685,7 @@ // domNode: undefined // htmlDlyCmdId: undefined // autohideId: undefined + // lastMousePosition: undefined // cmdsIgnored }, solution: { // Static Object: Defines the solutions built in jPlayer. @@ -2501,13 +2502,30 @@ event = "mousemove.jPlayer", namespace = ".jPlayerAutohide", eventType = event + namespace, - handler = function() { + handler = function(sourceEvent) { + var mouseMoved = false; + if (self.internal.lastMousePosition != undefined) { + //get the change from last position to this position + var deltaX = self.internal.lastMousePosition.x - sourceEvent.clientX, + deltaY = self.internal.lastMousePosition.y - sourceEvent.clientY; + mouseMoved = (Math.abs(deltaX)>0) || (Math.abs(deltaY)>0); + } else { + mouseMoved = true; + } + // store current position for next method execution + self.internal.lastMousePosition = { + x : sourceEvent.clientX, + y : sourceEvent.clientY + }; + // if mouse has been actually moved, do the gui fadeIn/fadeOut + if (mouseMoved) { self.css.jq.gui.fadeIn(self.options.autohide.fadeIn, function() { clearTimeout(self.internal.autohideId); self.internal.autohideId = setTimeout( function() { self.css.jq.gui.fadeOut(self.options.autohide.fadeOut); }, self.options.autohide.hold); }); + } }; if(this.css.jq.gui.length) { @@ -2518,6 +2536,8 @@ // Removes the fadeOut operation from the fadeIn callback. clearTimeout(this.internal.autohideId); + // undefine lastMousePosition + delete this.internal.lastMousePosition; this.element.unbind(namespace); this.css.jq.gui.unbind(namespace); From 0b82804315de6f315fea2139f54d3360cadc95c9 Mon Sep 17 00:00:00 2001 From: Mark Panaghiston Date: Fri, 30 May 2014 19:15:16 +0100 Subject: [PATCH 024/187] Fixed #206 jPlayer breaks links for keyboard users. --- bower.json | 2 +- jplayer.jquery.json | 2 +- jquery.jplayer/jquery.jplayer.js | 28 ++++++++++++++++++---------- package.json | 2 +- 4 files changed, 21 insertions(+), 13 deletions(-) diff --git a/bower.json b/bower.json index 44acc064..466bc075 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "jPlayer", - "version": "2.6.1", + "version": "2.6.2", "main": [ "./jquery.jplayer/jquery.jplayer.js", "./skin/pink.flag/jplayer.pink.flag.css" diff --git a/jplayer.jquery.json b/jplayer.jquery.json index 05f5f00b..10ab58c7 100644 --- a/jplayer.jquery.json +++ b/jplayer.jquery.json @@ -11,7 +11,7 @@ "html5", "streaming" ], - "version": "2.6.1", + "version": "2.6.2", "author": { "name": "Mark J Panaghiston", "email": "markp@happyworm.com", diff --git a/jquery.jplayer/jquery.jplayer.js b/jquery.jplayer/jquery.jplayer.js index facb17a4..c0a21c6a 100644 --- a/jquery.jplayer/jquery.jplayer.js +++ b/jquery.jplayer/jquery.jplayer.js @@ -7,8 +7,8 @@ * http://opensource.org/licenses/MIT * * Author: Mark J Panaghiston - * Version: 2.6.1 - * Date: 3rd April 2014 + * Version: 2.6.2 + * Date: 30th May 2014 */ /* Code verified using http://www.jshint.com/ */ @@ -423,25 +423,33 @@ // The current jPlayer instance in focus. $.jPlayer.focus = null; - // The list of element node names to ignore with key controls. - $.jPlayer.keyIgnoreElementNames = "INPUT TEXTAREA"; + // (fallback) The list of element node names to ignore with key controls. + $.jPlayer.keyIgnoreElementNames = "A INPUT TEXTAREA SELECT BUTTON"; // The function that deals with key presses. var keyBindings = function(event) { var f = $.jPlayer.focus, + pageFocus = document.activeElement, ignoreKey; // A jPlayer instance must be in focus. ie., keyEnabled and the last one played. if(f) { // What generated the key press? - $.each( $.jPlayer.keyIgnoreElementNames.split(/\s+/g), function(i, name) { - // The strings should already be uppercase. - if(event.target.nodeName.toUpperCase() === name.toUpperCase()) { + if(typeof pageFocus !== 'undefined') { + if(typeof pageFocus !== 'null' && pageFocus.nodeName.toUpperCase() !== "BODY") { ignoreKey = true; - return false; // exit each. } - }); + } else { + // Fallback for no document.activeElement support. + $.each( $.jPlayer.keyIgnoreElementNames.split(/\s+/g), function(i, name) { + // The strings should already be uppercase. + if(event.target.nodeName.toUpperCase() === name.toUpperCase()) { + ignoreKey = true; + return false; // exit each. + } + }); + } if(!ignoreKey) { // See if the key pressed matches any of the bindings. $.each(f.options.keyBindings, function(action, binding) { @@ -471,7 +479,7 @@ $.jPlayer.prototype = { count: 0, // Static Variable: Change it via prototype. version: { // Static Object - script: "2.6.1", + script: "2.6.2", needFlash: "2.6.0", flash: "unknown" }, diff --git a/package.json b/package.json index de73ef5f..5aed5dd0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jplayer", - "version": "2.6.1", + "version": "2.6.2", "description": "The jQuery HTML5 Audio / Video Library", "homepage": "http://www.jplayer.org/", "keywords": [ From d0762d83ea9863e7eae30256a26d2c4071359301 Mon Sep 17 00:00:00 2001 From: Mark Panaghiston Date: Fri, 30 May 2014 20:36:33 +0100 Subject: [PATCH 025/187] Added autoBlur option to control blur after user action. --- jquery.jplayer/jquery.jplayer.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/jquery.jplayer/jquery.jplayer.js b/jquery.jplayer/jquery.jplayer.js index c0a21c6a..3cc36975 100644 --- a/jquery.jplayer/jquery.jplayer.js +++ b/jquery.jplayer/jquery.jplayer.js @@ -524,6 +524,7 @@ gui: ".jp-gui", // The interface used with autohide feature. noSolution: ".jp-no-solution" // For error feedback when jPlayer cannot find a solution. }, + autoBlur: true, // GUI control handlers will drop focus after clicks. smoothPlayBar: false, // Smooths the play bar transitions, which affects clicks and short media with big changes per second. fullScreen: false, // Native Full Screen fullWindow: false, @@ -2143,7 +2144,9 @@ var handler = function(e) { e.preventDefault(); self[fn](e); - $(this).blur(); + if(self.options.autoBlur) { + $(this).blur(); + } }; this.css.jq[fn].bind("click.jPlayer", handler); // Using jPlayer namespace } @@ -2453,6 +2456,9 @@ case "audioFullScreen" : this.options[key] = value; break; + case "autoBlur" : + this.options[key] = value; + break; } return this; From 2cb72ad56e7cdf09d3b1bb94e647750fc609942c Mon Sep 17 00:00:00 2001 From: Mark Panaghiston Date: Fri, 30 May 2014 20:51:10 +0100 Subject: [PATCH 026/187] Fixed syntax of null test for #206 fix --- bower.json | 2 +- jplayer.jquery.json | 2 +- jquery.jplayer/jquery.jplayer.js | 6 +++--- package.json | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bower.json b/bower.json index 466bc075..1e846133 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "jPlayer", - "version": "2.6.2", + "version": "2.6.3", "main": [ "./jquery.jplayer/jquery.jplayer.js", "./skin/pink.flag/jplayer.pink.flag.css" diff --git a/jplayer.jquery.json b/jplayer.jquery.json index 10ab58c7..f049b577 100644 --- a/jplayer.jquery.json +++ b/jplayer.jquery.json @@ -11,7 +11,7 @@ "html5", "streaming" ], - "version": "2.6.2", + "version": "2.6.3", "author": { "name": "Mark J Panaghiston", "email": "markp@happyworm.com", diff --git a/jquery.jplayer/jquery.jplayer.js b/jquery.jplayer/jquery.jplayer.js index c0a21c6a..59deac41 100644 --- a/jquery.jplayer/jquery.jplayer.js +++ b/jquery.jplayer/jquery.jplayer.js @@ -7,7 +7,7 @@ * http://opensource.org/licenses/MIT * * Author: Mark J Panaghiston - * Version: 2.6.2 + * Version: 2.6.3 * Date: 30th May 2014 */ @@ -437,7 +437,7 @@ if(f) { // What generated the key press? if(typeof pageFocus !== 'undefined') { - if(typeof pageFocus !== 'null' && pageFocus.nodeName.toUpperCase() !== "BODY") { + if(pageFocus !== null && pageFocus.nodeName.toUpperCase() !== "BODY") { ignoreKey = true; } } else { @@ -479,7 +479,7 @@ $.jPlayer.prototype = { count: 0, // Static Variable: Change it via prototype. version: { // Static Object - script: "2.6.2", + script: "2.6.3", needFlash: "2.6.0", flash: "unknown" }, diff --git a/package.json b/package.json index 5aed5dd0..3dc487e9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jplayer", - "version": "2.6.2", + "version": "2.6.3", "description": "The jQuery HTML5 Audio / Video Library", "homepage": "http://www.jplayer.org/", "keywords": [ From b2389b0de13cd7cc5105f284f224401e4aafc33d Mon Sep 17 00:00:00 2001 From: Mark Panaghiston Date: Sun, 8 Jun 2014 21:12:22 +0100 Subject: [PATCH 027/187] New Feature: Added useStateClassSkin option etc to enable ARIA skins --- jquery.jplayer/jquery.jplayer.js | 92 ++++++++++++++++++++++++++------ 1 file changed, 77 insertions(+), 15 deletions(-) diff --git a/jquery.jplayer/jquery.jplayer.js b/jquery.jplayer/jquery.jplayer.js index 298b132f..34a6e810 100644 --- a/jquery.jplayer/jquery.jplayer.js +++ b/jquery.jplayer/jquery.jplayer.js @@ -524,6 +524,14 @@ gui: ".jp-gui", // The interface used with autohide feature. noSolution: ".jp-no-solution" // For error feedback when jPlayer cannot find a solution. }, + stateClass: { // Classes added to the cssSelectorAncestor to indicate the state. + playing: "jp-state-playing", + seeking: "jp-state-seeking", + muted: "jp-state-muted", + looped: "jp-state-looped", + fullScreen: "jp-state-full-screen" + }, + useStateClassSkin: false, // A state class skin relies on the state classes to change the visual appearance. The single control toggles the effect, for example: play then pause, mute then unmute. autoBlur: true, // GUI control handlers will drop focus after clicks. smoothPlayBar: false, // Smooths the play bar transitions, which affects clicks and short media with big changes per second. fullScreen: false, // Native Full Screen @@ -1625,6 +1633,23 @@ } else { this.status.paused = !playing; } + // Apply the state classes. (For the useStateClassSkin:true option) + if(playing) { + this.addStateClass('playing'); + } else { + this.removeStateClass('playing'); + } + if(!this.status.noFullWindow && this.options.fullWindow) { + this.addStateClass('fullScreen'); + } else { + this.removeStateClass('fullScreen'); + } + if(this.options.loop) { + this.addStateClass('looped'); + } else { + this.removeStateClass('looped'); + } + // Toggle the GUI element pairs. (For the useStateClassSkin:false option) if(this.css.jq.play.length && this.css.jq.pause.length) { if(playing) { this.css.jq.play.hide(); @@ -1703,11 +1728,13 @@ if(this.css.jq.seekBar.length) { this.css.jq.seekBar.addClass("jp-seeking-bg"); } + this.addStateClass('seeking'); }, _seeked: function() { if(this.css.jq.seekBar.length) { this.css.jq.seekBar.removeClass("jp-seeking-bg"); } + this.removeStateClass('seeking'); }, _resetGate: function() { this.html.audio.gate = false; @@ -1735,6 +1762,16 @@ }); return media; }, + addStateClass: function(state) { + if(this.ancestorJq.length) { + this.ancestorJq.addClass(this.options.stateClass[state]); + } + }, + removeStateClass: function(state) { + if(this.ancestorJq.length) { + this.ancestorJq.removeClass(this.options.stateClass[state]); + } + }, setMedia: function(media) { /* media[format] = String: URL of format. Must contain all of the supplied option's video or audio formats. @@ -1892,16 +1929,21 @@ } }, play: function(time) { - time = (typeof time === "number") ? time : NaN; // Remove jQuery event from click handler - if(this.status.srcSet) { - this.focus(); - if(this.html.active) { - this._html_play(time); - } else if(this.flash.active) { - this._flash_play(time); - } + var guiAction = typeof time === "object"; // Flags GUI click events so we know this was not a direct command, but an action taken by the user on the GUI. + if(guiAction && this.options.useStateClassSkin && !this.status.paused) { + this.pause(time); // The time would be the click event, but passing it over so info is not lost. } else { - this._urlNotSetError("play"); + time = (typeof time === "number") ? time : NaN; // Remove jQuery event from click handler + if(this.status.srcSet) { + this.focus(); + if(this.html.active) { + this._html_play(time); + } else if(this.flash.active) { + this._flash_play(time); + } + } else { + this._urlNotSetError("play"); + } } }, videoPlay: function() { // Handles clicks on the play button over the video poster @@ -1995,8 +2037,13 @@ } }, mute: function(mute) { // mute is either: undefined (true), an event object (true) or a boolean (muted). - mute = mute === undefined ? true : !!mute; - this._muted(mute); + var guiAction = typeof mute === "object"; // Flags GUI click events so we know this was not a direct command, but an action taken by the user on the GUI. + if(guiAction && this.options.useStateClassSkin && this.options.muted) { + this._muted(false); + } else { + mute = mute === undefined ? true : !!mute; + this._muted(mute); + } }, unmute: function(unmute) { // unmute is either: undefined (true), an event object (true) or a boolean (!muted). unmute = unmute === undefined ? true : !!unmute; @@ -2006,6 +2053,11 @@ if(mute === undefined) { mute = this.options.muted; } + if(mute) { + this.addStateClass('muted'); + } else { + this.removeStateClass('muted'); + } if(this.css.jq.mute.length && this.css.jq.unmute.length) { if(this.status.noVolume) { this.css.jq.mute.hide(); @@ -2237,8 +2289,13 @@ } } }, - repeat: function() { // Handle clicks on the repeat button - this._loop(true); + repeat: function(event) { // Handle clicks on the repeat button + var guiAction = typeof event === "object"; // Flags GUI click events so we know this was not a direct command, but an action taken by the user on the GUI. + if(guiAction && this.options.useStateClassSkin && this.options.loop) { + this._loop(false); + } else { + this._loop(true); + } }, repeatOff: function() { // Handle clicks on the repeatOff button this._loop(false); @@ -2549,8 +2606,13 @@ } } }, - fullScreen: function() { - this._setOption("fullScreen", true); + fullScreen: function(event) { + var guiAction = typeof event === "object"; // Flags GUI click events so we know this was not a direct command, but an action taken by the user on the GUI. + if(guiAction && this.options.useStateClassSkin && this.options.fullScreen) { + this._setOption("fullScreen", false); + } else { + this._setOption("fullScreen", true); + } }, restoreScreen: function() { this._setOption("fullScreen", false); From 48c478deb386cd3a4ccbc01e46bcaa06811aaf13 Mon Sep 17 00:00:00 2001 From: Mark Panaghiston Date: Sun, 8 Jun 2014 21:54:41 +0100 Subject: [PATCH 028/187] Cleaned up the Playlist add-on code now that jPlayer deals with the title --- add-on/jplayer.playlist.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/add-on/jplayer.playlist.js b/add-on/jplayer.playlist.js index 60d48736..48e5cd0f 100644 --- a/add-on/jplayer.playlist.js +++ b/add-on/jplayer.playlist.js @@ -2,17 +2,17 @@ * Playlist Object for the jPlayer Plugin * http://www.jplayer.org * - * Copyright (c) 2009 - 2013 Happyworm Ltd + * Copyright (c) 2009 - 2014 Happyworm Ltd * Licensed under the MIT license. * http://www.opensource.org/licenses/MIT * * Author: Mark J Panaghiston - * Version: 2.3.0 - * Date: 20th April 2013 + * Version: 2.3.1 + * Date: 8th June 2014 * * Requires: * - jQuery 1.7.0+ - * - jPlayer 2.3.0+ + * - jPlayer 2.7.0+ */ /* Code verified using http://www.jshint.com/ */ @@ -53,7 +53,7 @@ this._initPlaylist(playlist); // Copies playlist to this.original. Then mirrors this.original to this.playlist. Creating two arrays, where the element pointers match. (Enables pointer comparison.) // Setup the css selectors for the extra interface items used by the playlist. - this.cssSelector.title = this.cssSelector.cssSelectorAncestor + " .jp-title"; // Note that the text is written to the decendant li node. + this.cssSelector.details = this.cssSelector.cssSelectorAncestor + " .jp-details"; // Note that jPlayer controls the text in the title element. this.cssSelector.playlist = this.cssSelector.cssSelectorAncestor + " .jp-playlist"; this.cssSelector.next = this.cssSelector.cssSelectorAncestor + " .jp-next"; this.cssSelector.previous = this.cssSelector.cssSelectorAncestor + " .jp-previous"; @@ -86,9 +86,9 @@ // Create a resize event handler to show the title in full screen mode. $(this.cssSelector.jPlayer).bind($.jPlayer.event.resize, function(event) { if(event.jPlayer.options.fullScreen) { - $(self.cssSelector.title).show(); + $(self.cssSelector.details).show(); } else { - $(self.cssSelector.title).hide(); + $(self.cssSelector.details).hide(); } }); @@ -116,7 +116,7 @@ // Put the title in its initial display state if(!this.options.fullScreen) { - $(this.cssSelector.title).hide(); + $(this.cssSelector.details).hide(); } // Remove the empty
  • from the page HTML. Allows page to be valid HTML, while not interfereing with display animations @@ -311,7 +311,7 @@ if(this.playlist.length && index !== undefined) { $(this.cssSelector.playlist + " .jp-playlist-current").removeClass("jp-playlist-current"); $(this.cssSelector.playlist + " li:nth-child(" + (index + 1) + ")").addClass("jp-playlist-current").find(".jp-playlist-item").addClass("jp-playlist-current"); - $(this.cssSelector.title + " li").html(this.playlist[index].title + (this.playlist[index].artist ? " " : "")); + // $(this.cssSelector.details + " li").html("" + (this.playlist[index].artist ? " " : "")); } }, setPlaylist: function(playlist) { From 2216c7cba1e6cb1a6026e89822547bf3aa3b19d6 Mon Sep 17 00:00:00 2001 From: Mark Panaghiston Date: Sun, 8 Jun 2014 22:16:37 +0100 Subject: [PATCH 029/187] Updated playlist to use the autoBlur option of jplayer core --- add-on/jplayer.playlist.js | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/add-on/jplayer.playlist.js b/add-on/jplayer.playlist.js index 48e5cd0f..428e4b78 100644 --- a/add-on/jplayer.playlist.js +++ b/add-on/jplayer.playlist.js @@ -92,26 +92,35 @@ } }); + // The blur function executes in the context of the click handler + var blur = function() { + if($(this.cssSelector.jPlayer).jPlayer("option", "autoBlur")) { + $(this).blur(); + } + }; + // Create click handlers for the extra buttons that do playlist functions. - $(this.cssSelector.previous).click(function() { + $(this.cssSelector.previous).click(function(e) { + e.preventDefault(); self.previous(); - $(this).blur(); - return false; + blur.call(this); }); - $(this.cssSelector.next).click(function() { + $(this.cssSelector.next).click(function(e) { + e.preventDefault(); self.next(); - $(this).blur(); - return false; + blur.call(this); }); - $(this.cssSelector.shuffle).click(function() { + $(this.cssSelector.shuffle).click(function(e) { + e.preventDefault(); self.shuffle(true); - return false; + blur.call(this); }); - $(this.cssSelector.shuffleOff).click(function() { + $(this.cssSelector.shuffleOff).click(function(e) { + e.preventDefault(); self.shuffle(false); - return false; + blur.call(this); }).hide(); // Put the title in its initial display state From 54408ecfb11d2f5b19132f8fcbcbdd9992bd33ce Mon Sep 17 00:00:00 2001 From: Mark Panaghiston Date: Sun, 8 Jun 2014 23:02:25 +0100 Subject: [PATCH 030/187] Playlist - Fixed bug in autoBlur - Added useStateClassSkin support. --- add-on/jplayer.playlist.js | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/add-on/jplayer.playlist.js b/add-on/jplayer.playlist.js index 428e4b78..45b29b8e 100644 --- a/add-on/jplayer.playlist.js +++ b/add-on/jplayer.playlist.js @@ -7,7 +7,7 @@ * http://www.opensource.org/licenses/MIT * * Author: Mark J Panaghiston - * Version: 2.3.1 + * Version: 2.3.2 * Date: 8th June 2014 * * Requires: @@ -44,6 +44,9 @@ self.previous(); } } + }, + stateClass: { + shuffled: "jp-state-shuffled" } }, this._options, options); // Object: The jPlayer constructor options for this playlist and the playlist options @@ -94,7 +97,7 @@ // The blur function executes in the context of the click handler var blur = function() { - if($(this.cssSelector.jPlayer).jPlayer("option", "autoBlur")) { + if($(self.cssSelector.jPlayer).jPlayer("option", "autoBlur")) { $(this).blur(); } }; @@ -114,7 +117,11 @@ $(this.cssSelector.shuffle).click(function(e) { e.preventDefault(); - self.shuffle(true); + if(self.shuffled && $(self.cssSelector.jPlayer).jPlayer("option", "useStateClassSkin")) { + self.shuffle(false); + } else { + self.shuffle(true); + } blur.call(this); }); $(this.cssSelector.shuffleOff).click(function(e) { @@ -308,12 +315,20 @@ } else { $(this.cssSelector.playlist + " ." + this.options.playlistOptions.removeItemClass).hide(); } + if(this.shuffled) { - $(this.cssSelector.shuffleOff).show(); - $(this.cssSelector.shuffle).hide(); + $(this.cssSelector.jPlayer).jPlayer("addStateClass", "shuffled"); } else { - $(this.cssSelector.shuffleOff).hide(); - $(this.cssSelector.shuffle).show(); + $(this.cssSelector.jPlayer).jPlayer("removeStateClass", "shuffled"); + } + if($(this.cssSelector.shuffle).length && $(this.cssSelector.shuffleOff).length) { + if(this.shuffled) { + $(this.cssSelector.shuffleOff).show(); + $(this.cssSelector.shuffle).hide(); + } else { + $(this.cssSelector.shuffleOff).hide(); + $(this.cssSelector.shuffle).show(); + } } }, _highlight: function(index) { From e7a426d48d13f2bfaaebc68627d6c65ddd86e458 Mon Sep 17 00:00:00 2001 From: Mark Panaghiston Date: Mon, 9 Jun 2014 18:04:12 +0100 Subject: [PATCH 031/187] Playlist - autoBlur now affects the track selection, free DL and remove links. --- add-on/jplayer.playlist.js | 42 ++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/add-on/jplayer.playlist.js b/add-on/jplayer.playlist.js index 45b29b8e..f6e886ac 100644 --- a/add-on/jplayer.playlist.js +++ b/add-on/jplayer.playlist.js @@ -7,8 +7,8 @@ * http://www.opensource.org/licenses/MIT * * Author: Mark J Panaghiston - * Version: 2.3.2 - * Date: 8th June 2014 + * Version: 2.3.3 + * Date: 9th June 2014 * * Requires: * - jQuery 1.7.0+ @@ -95,24 +95,17 @@ } }); - // The blur function executes in the context of the click handler - var blur = function() { - if($(self.cssSelector.jPlayer).jPlayer("option", "autoBlur")) { - $(this).blur(); - } - }; - // Create click handlers for the extra buttons that do playlist functions. $(this.cssSelector.previous).click(function(e) { e.preventDefault(); self.previous(); - blur.call(this); + self.blur(this); }); $(this.cssSelector.next).click(function(e) { e.preventDefault(); self.next(); - blur.call(this); + self.blur(this); }); $(this.cssSelector.shuffle).click(function(e) { @@ -122,12 +115,12 @@ } else { self.shuffle(true); } - blur.call(this); + self.blur(this); }); $(this.cssSelector.shuffleOff).click(function(e) { e.preventDefault(); self.shuffle(false); - blur.call(this); + self.blur(this); }).hide(); // Put the title in its initial display state @@ -283,30 +276,30 @@ _createItemHandlers: function() { var self = this; // Create live handlers for the playlist items - $(this.cssSelector.playlist).off("click", "a." + this.options.playlistOptions.itemClass).on("click", "a." + this.options.playlistOptions.itemClass, function() { + $(this.cssSelector.playlist).off("click", "a." + this.options.playlistOptions.itemClass).on("click", "a." + this.options.playlistOptions.itemClass, function(e) { + e.preventDefault(); var index = $(this).parent().parent().index(); if(self.current !== index) { self.play(index); } else { $(self.cssSelector.jPlayer).jPlayer("play"); } - $(this).blur(); - return false; + self.blur(this); }); // Create live handlers that disable free media links to force access via right click - $(this.cssSelector.playlist).off("click", "a." + this.options.playlistOptions.freeItemClass).on("click", "a." + this.options.playlistOptions.freeItemClass, function() { + $(this.cssSelector.playlist).off("click", "a." + this.options.playlistOptions.freeItemClass).on("click", "a." + this.options.playlistOptions.freeItemClass, function(e) { + e.preventDefault(); $(this).parent().parent().find("." + self.options.playlistOptions.itemClass).click(); - $(this).blur(); - return false; + self.blur(this); }); // Create live handlers for the remove controls - $(this.cssSelector.playlist).off("click", "a." + this.options.playlistOptions.removeItemClass).on("click", "a." + this.options.playlistOptions.removeItemClass, function() { + $(this.cssSelector.playlist).off("click", "a." + this.options.playlistOptions.removeItemClass).on("click", "a." + this.options.playlistOptions.removeItemClass, function(e) { + e.preventDefault(); var index = $(this).parent().parent().index(); self.remove(index); - $(this).blur(); - return false; + self.blur(this); }); }, _updateControls: function() { @@ -489,6 +482,11 @@ $(this).slideDown(self.options.playlistOptions.shuffleTime); }); } + }, + blur: function(that) { + if($(this.cssSelector.jPlayer).jPlayer("option", "autoBlur")) { + $(that).blur(); + } } }; })(jQuery); From cf430360126765a06cd6a20d34ba23753479c0c2 Mon Sep 17 00:00:00 2001 From: Mark Panaghiston Date: Mon, 9 Jun 2014 18:11:39 +0100 Subject: [PATCH 032/187] Playlist - removed tabindex from free DL link and changed track link to tabindex 0 --- add-on/jplayer.playlist.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/add-on/jplayer.playlist.js b/add-on/jplayer.playlist.js index f6e886ac..057f3eae 100644 --- a/add-on/jplayer.playlist.js +++ b/add-on/jplayer.playlist.js @@ -7,7 +7,7 @@ * http://www.opensource.org/licenses/MIT * * Author: Mark J Panaghiston - * Version: 2.3.3 + * Version: 2.3.4 * Date: 9th June 2014 * * Requires: @@ -261,14 +261,14 @@ } else { listItem += " | "; } - listItem += "" + property + ""; + listItem += "" + property + ""; } }); listItem += ")"; } // The title is given next in the HTML otherwise the float:right on the free media corrupts in IE6/7 - listItem += "" + media.title + (media.artist ? " " : "") + ""; + listItem += "" + media.title + (media.artist ? " " : "") + ""; listItem += "
  • "; return listItem; From 05266019b6310544eee45b86133a3d1c19920d28 Mon Sep 17 00:00:00 2001 From: Mark Panaghiston Date: Mon, 9 Jun 2014 18:26:21 +0100 Subject: [PATCH 033/187] Playlist - Fixed free DL link's tabindex to -1 to remove it from order. --- add-on/jplayer.playlist.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/add-on/jplayer.playlist.js b/add-on/jplayer.playlist.js index 057f3eae..e5ea0ba2 100644 --- a/add-on/jplayer.playlist.js +++ b/add-on/jplayer.playlist.js @@ -261,7 +261,7 @@ } else { listItem += " | "; } - listItem += "" + property + ""; + listItem += "" + property + ""; } }); listItem += ")"; From 4fe69b22fc2f2fe195fe7423609d906f89ada94b Mon Sep 17 00:00:00 2001 From: Mark Panaghiston Date: Fri, 1 Aug 2014 16:59:26 +0100 Subject: [PATCH 034/187] Fixed HTML canPlayType test for MP3. Only use audio/mpeg. No codecs. --- bower.json | 2 +- jplayer.jquery.json | 2 +- jquery.jplayer/jquery.jplayer.js | 8 ++++---- package.json | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/bower.json b/bower.json index 1e846133..94030381 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "jPlayer", - "version": "2.6.3", + "version": "2.6.4", "main": [ "./jquery.jplayer/jquery.jplayer.js", "./skin/pink.flag/jplayer.pink.flag.css" diff --git a/jplayer.jquery.json b/jplayer.jquery.json index f049b577..7028450b 100644 --- a/jplayer.jquery.json +++ b/jplayer.jquery.json @@ -11,7 +11,7 @@ "html5", "streaming" ], - "version": "2.6.3", + "version": "2.6.4", "author": { "name": "Mark J Panaghiston", "email": "markp@happyworm.com", diff --git a/jquery.jplayer/jquery.jplayer.js b/jquery.jplayer/jquery.jplayer.js index 59deac41..aff47731 100644 --- a/jquery.jplayer/jquery.jplayer.js +++ b/jquery.jplayer/jquery.jplayer.js @@ -7,8 +7,8 @@ * http://opensource.org/licenses/MIT * * Author: Mark J Panaghiston - * Version: 2.6.3 - * Date: 30th May 2014 + * Version: 2.6.4 + * Date: 1st August 2014 */ /* Code verified using http://www.jshint.com/ */ @@ -479,7 +479,7 @@ $.jPlayer.prototype = { count: 0, // Static Variable: Change it via prototype. version: { // Static Object - script: "2.6.3", + script: "2.6.4", needFlash: "2.6.0", flash: "unknown" }, @@ -702,7 +702,7 @@ // 'MPEG-4 support' : canPlayType('video/mp4; codecs="mp4v.20.8"') format: { // Static Object mp3: { - codec: 'audio/mpeg; codecs="mp3"', + codec: 'audio/mpeg', flashCanPlay: true, media: 'audio' }, diff --git a/package.json b/package.json index 3dc487e9..428e5f01 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jplayer", - "version": "2.6.3", + "version": "2.6.4", "description": "The jQuery HTML5 Audio / Video Library", "homepage": "http://www.jplayer.org/", "keywords": [ From 4a90991bb68336663a85e98788fec83edeb5e723 Mon Sep 17 00:00:00 2001 From: Pascal Thormeier Date: Thu, 28 Aug 2014 22:34:25 +0200 Subject: [PATCH 035/187] Add composer.json --- composer.json | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 composer.json diff --git a/composer.json b/composer.json new file mode 100644 index 00000000..1b7d18e7 --- /dev/null +++ b/composer.json @@ -0,0 +1,21 @@ +{ + "name": "happyworm/jPlayer", + "type": "component", + "require": { + "robloach/component-installer": "*", + "components/jquery": ">=1.9" + }, + "extra": { + "component": { + "scripts": [ + "jquery.jplayer/jquery.jplayer.js" + ], + "files": [ + "jquery.jplayer/Jplayer.swf", + "add-on/*.js", + "popcorn/player/*.js", + "skin/*" + ] + } + } +} \ No newline at end of file From 0dfc8f507a13bf643c6998c7bb87288bd835cc8a Mon Sep 17 00:00:00 2001 From: Pascal Thormeier Date: Thu, 28 Aug 2014 22:53:07 +0200 Subject: [PATCH 036/187] Add support for delivered skins and update README --- README.md | 29 +++++++++++++++++++++++++++++ composer.json | 3 ++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e674e58b..8eb40179 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,35 @@ _(*) Optional counterpart formats to increase HTML5 cross-browser support._ * simple install using `bower install jplayer` * see for more information. +## Composer install + +Install jPlayer via composer by adding the following lines to your `composer.json` in your project: + + // ... + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/happyworm/jPlayer" + } + ] + // ... + "require": { + // ... + "happyworm/jPlayer": "2.*" + // ... + } + // ... + "config": { + "component-dir": "your/desired/asset/path" + }, + // ... + +Then execute the following: + + php composer.phar update + +Composer will now download all components and install the needed files into `your/desired/asset/path`, ready to use. + ## License [jPlayer](http://jplayer.org/) is licensed under the [MIT license](http://opensource.org/licenses/MIT). diff --git a/composer.json b/composer.json index 1b7d18e7..a8fb7238 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,8 @@ "jquery.jplayer/Jplayer.swf", "add-on/*.js", "popcorn/player/*.js", - "skin/*" + "skin/blue.monday/*", + "skin/pink.flag/*" ] } } From 94e3c7a838a6124c779e7bdb1efc16454a0383e9 Mon Sep 17 00:00:00 2001 From: Mark Panaghiston Date: Mon, 1 Sep 2014 19:07:54 +0100 Subject: [PATCH 037/187] Updated versioning to 2.7.0 --- actionscript/Jplayer.as | 7 ++++--- .../happyworm/jPlayer/JplayerStatus.as | 4 ++-- add-on/jplayer.playlist.js | 4 ++-- jquery.jplayer/Jplayer.swf | Bin 14162 -> 14170 bytes jquery.jplayer/jquery.jplayer.js | 8 ++++---- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/actionscript/Jplayer.as b/actionscript/Jplayer.as index c621aace..7af76180 100644 --- a/actionscript/Jplayer.as +++ b/actionscript/Jplayer.as @@ -7,8 +7,8 @@ * http://opensource.org/licenses/MIT * * Author: Mark J Panaghiston - * Version: 2.6.0 - * Date: 2nd April 2014 + * Version: 2.7.0 + * Date: 1st September 2014 * * FlashVars expected: (AS3 property of: loaderInfo.parameters) * id: (URL Encoded: String) Id of jPlayer instance @@ -16,7 +16,8 @@ * muted: (Boolean in a String) Sets the initial muted state * jQuery: (URL Encoded: String) Sets the jQuery var name. Used with: someVar = jQuery.noConflict(true); The someVar name must contain jQuery in it. * - * Compiled using: Adobe Flex Compiler (mxmlc) Version 4.5.1 build 21328 + * Compiled using: Adobe Flex Compiler (mxmlc) Version 4.6 + * mxmlc Jplayer.as -static-link-runtime-shared-libraries=true */ package { diff --git a/actionscript/happyworm/jPlayer/JplayerStatus.as b/actionscript/happyworm/jPlayer/JplayerStatus.as index 909816a7..6fc20678 100644 --- a/actionscript/happyworm/jPlayer/JplayerStatus.as +++ b/actionscript/happyworm/jPlayer/JplayerStatus.as @@ -7,13 +7,13 @@ * http://opensource.org/licenses/MIT * * Author: Mark J Panaghiston - * Date: 2nd April 2014 + * Date: 1st September 2014 */ package happyworm.jPlayer { public class JplayerStatus { - public static const VERSION:String = "2.6.0"; // The version of the Flash jPlayer entity. + public static const VERSION:String = "2.7.0"; // The version of the Flash jPlayer entity. public var volume:Number = 0.5; // Not affected by reset() public var muted:Boolean = false; // Not affected by reset() diff --git a/add-on/jplayer.playlist.js b/add-on/jplayer.playlist.js index e5ea0ba2..4609f66d 100644 --- a/add-on/jplayer.playlist.js +++ b/add-on/jplayer.playlist.js @@ -7,8 +7,8 @@ * http://www.opensource.org/licenses/MIT * * Author: Mark J Panaghiston - * Version: 2.3.4 - * Date: 9th June 2014 + * Version: 2.4.0 + * Date: 1st September 2014 * * Requires: * - jQuery 1.7.0+ diff --git a/jquery.jplayer/Jplayer.swf b/jquery.jplayer/Jplayer.swf index 3012248067e63a1b0a9da3d2185d271de1fb8188..103a729071893d9d6e94d583103d2f0cccc0ebb4 100644 GIT binary patch literal 14170 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 z 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 T>B;x}&)h$EU(kA}RDS;-56>)h literal 14162 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 Uf>`J>z8Cw{RlfqfFUgPp2hU0Sp8x;= diff --git a/jquery.jplayer/jquery.jplayer.js b/jquery.jplayer/jquery.jplayer.js index 479bdb3a..779d7f3c 100644 --- a/jquery.jplayer/jquery.jplayer.js +++ b/jquery.jplayer/jquery.jplayer.js @@ -7,8 +7,8 @@ * http://opensource.org/licenses/MIT * * Author: Mark J Panaghiston - * Version: 2.6.4 - * Date: 1st August 2014 + * Version: 2.7.0 + * Date: 1st September 2014 */ /* Code verified using http://www.jshint.com/ */ @@ -479,8 +479,8 @@ $.jPlayer.prototype = { count: 0, // Static Variable: Change it via prototype. version: { // Static Object - script: "2.6.4", - needFlash: "2.6.0", + script: "2.7.0", + needFlash: "2.7.0", flash: "unknown" }, options: { // Instanced in $.jPlayer() constructor From e83d1dd92ceba41215fce41583d32a535a477a40 Mon Sep 17 00:00:00 2001 From: Mark Panaghiston Date: Mon, 1 Sep 2014 19:13:59 +0100 Subject: [PATCH 038/187] Updated popcorn player plugin versioning and mp3 codec fix. --- popcorn/player/popcorn.jplayer.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/popcorn/player/popcorn.jplayer.js b/popcorn/player/popcorn.jplayer.js index 154e1909..31fead47 100644 --- a/popcorn/player/popcorn.jplayer.js +++ b/popcorn/player/popcorn.jplayer.js @@ -7,11 +7,11 @@ * http://opensource.org/licenses/MIT * * Author: Mark J Panaghiston - * Version: 1.1.3 - * Date: 2nd April 2014 + * Version: 1.1.4 + * Date: 1st September 2014 * * For Popcorn Version: 1.3 - * For jPlayer Version: 2.6.0 + * For jPlayer Version: 2.7.0 * Requires: jQuery 1.7+ * Note: jQuery dependancy cannot be removed since jPlayer 2 is a jQuery plugin. Use of jQuery will be kept to a minimum. */ @@ -22,16 +22,16 @@ (function(Popcorn) { - var JQUERY_SCRIPT = '//code.jquery.com/jquery-1.11.0.min.js', // Used if jQuery not already present. - JPLAYER_SCRIPT = '//www.jplayer.org/2.6.0/js/jquery.jplayer.min.js', // Used if jPlayer not already present. - JPLAYER_SWFPATH = '//www.jplayer.org/2.6.0/js/Jplayer.swf', // Used if not specified in jPlayer options via SRC Object. + var JQUERY_SCRIPT = '//code.jquery.com/jquery-1.11.1.min.js', // Used if jQuery not already present. + JPLAYER_SCRIPT = '//www.jplayer.org/2.7.0/js/jquery.jplayer.min.js', // Used if jPlayer not already present. + JPLAYER_SWFPATH = '//www.jplayer.org/2.7.0/js/Jplayer.swf', // Used if not specified in jPlayer options via SRC Object. SOLUTION = 'html,flash', // The default solution option. DEBUG = false, // Decided to leave the debugging option and console output in for the time being. Overhead is trivial. jQueryDownloading = false, // Flag to stop multiple instances from each pulling in jQuery, thus corrupting it. jPlayerDownloading = false, // Flag to stop multiple instances from each pulling in jPlayer, thus corrupting it. format = { // Duplicate of jPlayer 2.5.0 object, to avoid always requiring jQuery and jPlayer to be loaded before performing the _canPlayType() test. mp3: { - codec: 'audio/mpeg; codecs="mp3"', + codec: 'audio/mpeg;', flashCanPlay: true, media: 'audio' }, From 05cb6145eaabe6dc01a2c312fd5815875bc67eae Mon Sep 17 00:00:00 2001 From: Mark Panaghiston Date: Mon, 1 Sep 2014 19:16:01 +0100 Subject: [PATCH 039/187] Updated package file versioning --- bower.json | 2 +- jplayer.jquery.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bower.json b/bower.json index 94030381..a4ed80cd 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "jPlayer", - "version": "2.6.4", + "version": "2.7.0", "main": [ "./jquery.jplayer/jquery.jplayer.js", "./skin/pink.flag/jplayer.pink.flag.css" diff --git a/jplayer.jquery.json b/jplayer.jquery.json index 7028450b..a729d14b 100644 --- a/jplayer.jquery.json +++ b/jplayer.jquery.json @@ -11,7 +11,7 @@ "html5", "streaming" ], - "version": "2.6.4", + "version": "2.7.0", "author": { "name": "Mark J Panaghiston", "email": "markp@happyworm.com", diff --git a/package.json b/package.json index 428e5f01..9c874398 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jplayer", - "version": "2.6.4", + "version": "2.7.0", "description": "The jQuery HTML5 Audio / Video Library", "homepage": "http://www.jplayer.org/", "keywords": [ From b2c841fb72edad738082cd07ec8b1b9d116e1b24 Mon Sep 17 00:00:00 2001 From: smidgen Date: Fri, 5 Sep 2014 16:59:02 -0400 Subject: [PATCH 040/187] Browser-compatibility fix for data URI scheme. Since MSIE cuts off URL's validated in this way at 5120 bytes, data URI's should not be qualified this way. This bugfix allows you to use base64-encoded data URI's for audio data. --- jquery.jplayer/jquery.jplayer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jquery.jplayer/jquery.jplayer.js b/jquery.jplayer/jquery.jplayer.js index 779d7f3c..dcd94c98 100644 --- a/jquery.jplayer/jquery.jplayer.js +++ b/jquery.jplayer/jquery.jplayer.js @@ -1756,7 +1756,7 @@ _absoluteMediaUrls: function(media) { var self = this; $.each(media, function(type, url) { - if(url && self.format[type]) { + if(url && self.format[type] && url.substr(0, 5) != 'data:') { media[type] = self._qualifyURL(url); } }); From f2c9f81545f81af80eb9338e8b3fb5ea130cdf62 Mon Sep 17 00:00:00 2001 From: smidgen Date: Fri, 12 Sep 2014 14:51:01 -0400 Subject: [PATCH 041/187] Bugfix for Chrome 37 As of Chrome version 37, the Android bugfix appears to be no longer needed, and in fact breaks jPlayer. This is the fix. --- jquery.jplayer/jquery.jplayer.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/jquery.jplayer/jquery.jplayer.js b/jquery.jplayer/jquery.jplayer.js index dcd94c98..6dee8edd 100644 --- a/jquery.jplayer/jquery.jplayer.js +++ b/jquery.jplayer/jquery.jplayer.js @@ -243,7 +243,16 @@ ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) || []; - return { browser: match[1] || "", version: match[2] || "0" }; + var oRet = { browser: match[1] || "", version: match[2] || "0" }; + + var rchrome = /(chrome)[ \/]([\w.]+)/; + var matchChrome = rchrome.exec(ua) || []; + + if (matchChrome[1]) { + oRet.chromeVersion = matchChrome[2]; + } + + return oRet; }; // Platform sniffer for detecting mobile devices @@ -277,6 +286,9 @@ if ( browserMatch.browser ) { $.jPlayer.browser[ browserMatch.browser ] = true; $.jPlayer.browser.version = browserMatch.version; + if (typeof(browserMatch.chromeVersion) != 'undefined') { + $.jPlayer.browser.chromeVersion = browserMatch.chromeVersion; + } } var platformMatch = $.jPlayer.uaPlatform(navigator.userAgent); if ( platformMatch.platform ) { @@ -1822,8 +1834,8 @@ self._html_setAudio(media); self.html.active = true; - // Setup the Android Fix - Only for HTML audio. - if($.jPlayer.platform.android) { + // Setup the Android Fix - Only for HTML audio. And it's not needed not for Chrome 37+. + if ( $.jPlayer.platform.android && !(typeof($.jPlayer.browser.chromeVersion) != 'undefined' && parseInt($.jPlayer.browser.chromeVersion) >= 37) ) { self.androidFix.setMedia = true; } } else { From 7ddf198cbacbe9ba5cb6287a4ddf8967f47e1a24 Mon Sep 17 00:00:00 2001 From: Mark Panaghiston Date: Fri, 19 Sep 2014 16:34:58 +0100 Subject: [PATCH 042/187] Fixed legacy Android fix to work with current Android. --- jquery.jplayer/jquery.jplayer.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/jquery.jplayer/jquery.jplayer.js b/jquery.jplayer/jquery.jplayer.js index 779d7f3c..0aea04da 100644 --- a/jquery.jplayer/jquery.jplayer.js +++ b/jquery.jplayer/jquery.jplayer.js @@ -7,8 +7,8 @@ * http://opensource.org/licenses/MIT * * Author: Mark J Panaghiston - * Version: 2.7.0 - * Date: 1st September 2014 + * Version: 2.7.1 + * Date: 19th September 2014 */ /* Code verified using http://www.jshint.com/ */ @@ -160,7 +160,7 @@ // "play", // jPlayer uses internally before bubbling. // "pause", // jPlayer uses internally before bubbling. "loadedmetadata", - "loadeddata", + // "loadeddata", // jPlayer uses internally before bubbling. // "waiting", // jPlayer uses internally before bubbling. // "playing", // jPlayer uses internally before bubbling. "canplay", @@ -479,7 +479,7 @@ $.jPlayer.prototype = { count: 0, // Static Variable: Change it via prototype. version: { // Static Object - script: "2.7.0", + script: "2.7.1", needFlash: "2.7.0", flash: "unknown" }, @@ -1295,6 +1295,13 @@ if(self.internal.cmdsIgnored && this.readyState > 0) { // Detect iOS executed the command self.internal.cmdsIgnored = false; } + self._getHtmlStatus(mediaElement); + self._updateInterface(); + self._trigger($.jPlayer.event.progress); + } + }, false); + mediaElement.addEventListener("loadeddata", function() { + if(entity.gate) { self.androidFix.setMedia = false; // Disable the fix after the first progress event. if(self.androidFix.play) { // Play Android audio - performing the fix. self.androidFix.play = false; @@ -1304,9 +1311,7 @@ self.androidFix.pause = false; self.pause(self.androidFix.time); } - self._getHtmlStatus(mediaElement); - self._updateInterface(); - self._trigger($.jPlayer.event.progress); + self._trigger($.jPlayer.event.loadeddata); } }, false); mediaElement.addEventListener("timeupdate", function() { From 5ebbd76b4e37145a20c28542b39ca7b8f2cac47a Mon Sep 17 00:00:00 2001 From: Mark Panaghiston Date: Fri, 19 Sep 2014 16:36:53 +0100 Subject: [PATCH 043/187] Updated package file versioning. --- bower.json | 2 +- jplayer.jquery.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bower.json b/bower.json index a4ed80cd..78d72c1f 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "jPlayer", - "version": "2.7.0", + "version": "2.7.1", "main": [ "./jquery.jplayer/jquery.jplayer.js", "./skin/pink.flag/jplayer.pink.flag.css" diff --git a/jplayer.jquery.json b/jplayer.jquery.json index a729d14b..abcdd66d 100644 --- a/jplayer.jquery.json +++ b/jplayer.jquery.json @@ -11,7 +11,7 @@ "html5", "streaming" ], - "version": "2.7.0", + "version": "2.7.1", "author": { "name": "Mark J Panaghiston", "email": "markp@happyworm.com", diff --git a/package.json b/package.json index 9c874398..27fa35be 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jplayer", - "version": "2.7.0", + "version": "2.7.1", "description": "The jQuery HTML5 Audio / Video Library", "homepage": "http://www.jplayer.org/", "keywords": [ From 4032921cf82adb771e959ebb1a1bca8bc200c8d8 Mon Sep 17 00:00:00 2001 From: Mark Panaghiston Date: Wed, 24 Sep 2014 19:02:01 +0100 Subject: [PATCH 044/187] Code Clean: Removed Flash TraceOut.as class and code. --- actionscript/Jplayer.as | 19 +----- actionscript/happyworm/jPlayer/TraceOut.as | 64 --------------------- jquery.jplayer/Jplayer.swf | Bin 14170 -> 13693 bytes 3 files changed, 1 insertion(+), 82 deletions(-) delete mode 100644 actionscript/happyworm/jPlayer/TraceOut.as diff --git a/actionscript/Jplayer.as b/actionscript/Jplayer.as index 7af76180..93727a7e 100644 --- a/actionscript/Jplayer.as +++ b/actionscript/Jplayer.as @@ -75,9 +75,6 @@ package { private var txLog:TextField; private var debug:Boolean = false; // Set debug to false for release compile! - private var localAIRDebug:Boolean = false; // This is autodetermined by AIR app - leave false! - - private var traceOut:TraceOut; // This class was found to cause problems on OSX with Firefox and Safari where more than 8 instances of the SWF are on a page. public function Jplayer() { @@ -156,8 +153,6 @@ package { myMp3Player.addEventListener(JplayerEvent.DEBUG_MSG, debugMsgHandler); myMp4Player.addEventListener(JplayerEvent.DEBUG_MSG, debugMsgHandler); myRtmpPlayer.addEventListener(JplayerEvent.DEBUG_MSG, debugMsgHandler); - - traceOut = new TraceOut(); // Instance it only when in debug mode. See comment above at var declaration. } } @@ -510,12 +505,6 @@ package { } } - private function tracer(msg:String):void { - if(debug) { - traceOut.tracer(msg); - } - } - private function extractStatusData(data:JplayerStatus):Object { var myStatus:Object = { version: JplayerStatus.VERSION, @@ -604,22 +593,16 @@ package { jPlayerFlashEvent(new JplayerEvent(JplayerEvent.JPLAYER_CLICK, myMp4Player.myStatus, "click")) } } - // This event is never called. See comments in class constructor. + // Handlers for context menu private function menuSelectHandler_jPlayer(e:ContextMenuEvent):void { navigateToURL(new URLRequest("http://jplayer.org/"), "_blank"); } - // This event is never called. See comments in class constructor. private function menuSelectHandler_happyworm(e:ContextMenuEvent):void { navigateToURL(new URLRequest("http://happyworm.com/"), "_blank"); } private function log(t:String):void { if(debug) { txLog.text = t + "\n" + txLog.text; - localAIRDebug = traceOut.localAIRDebug(); - if(localAIRDebug) { - tracer(t); - } - if(ExternalInterface.available && !securityIssue) { ExternalInterface.call("console.log", t); } diff --git a/actionscript/happyworm/jPlayer/TraceOut.as b/actionscript/happyworm/jPlayer/TraceOut.as deleted file mode 100644 index a66cca5b..00000000 --- a/actionscript/happyworm/jPlayer/TraceOut.as +++ /dev/null @@ -1,64 +0,0 @@ -/* - * jPlayer Plugin for jQuery JavaScript Library - * http://www.jplayer.org - * - * Copyright (c) 2009 - 2014 Happyworm Ltd - * Licensed under the MIT license. - * http://opensource.org/licenses/MIT - * - * Author: Robert M. Hall - * Date: 7th August 2012 - */ - -// This class was found to cause problems on OSX with Firefox and Safari where more than 8 instances of the SWF are on a page. - -package happyworm.jPlayer -{ - import flash.net.LocalConnection; - import flash.events.StatusEvent; - import flash.system.Capabilities; - import flash.utils.getTimer; - - public class TraceOut - { - - private var outgoing_lc:LocalConnection = new LocalConnection (); - private var firstEvent:Boolean = true; - private var _localAIRDebug:Boolean = false; - - public function TraceOut() - { - outgoing_lc.addEventListener(StatusEvent.STATUS, lcListener); - outgoing_lc.send("_log_output","startLogging",""); - } - - private function lcListener(event:StatusEvent):void - { - // Must have this listener to avoid errors - if (event.level == "error") - { - _localAIRDebug = false; - } - else if(event.level =="status" && firstEvent==true) - { - firstEvent = false; - tracer("<< Successful Connection To Event Logger >>"); - tracer("DEBUG INFO: \n<"+Capabilities.serverString + ">\nFlash Player Version: " + Capabilities.version + "\n"); - _localAIRDebug = true; - } - } - - public function localAIRDebug():Boolean - { - return _localAIRDebug; - } - - public function tracer(msg:String):void - { - trace(msg); - var outMsg:String = "[" + getTimer() + "ms] " + msg; - outgoing_lc.send("_log_output","displayMsg",outMsg); - - } - } -} diff --git a/jquery.jplayer/Jplayer.swf b/jquery.jplayer/Jplayer.swf index 103a729071893d9d6e94d583103d2f0cccc0ebb4..5862266538a37bda02b72bba03bbaba6d8c8878a 100644 GIT binary patch literal 13693 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 literal 14170 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 z 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 T>B;x}&)h$EU(kA}RDS;-56>)h From 564edf4aea312d15e31087e71fbe468bfb6e4cf5 Mon Sep 17 00:00:00 2001 From: Afterster Date: Thu, 12 Dec 2013 18:53:27 +0100 Subject: [PATCH 045/187] Return good ratio in Flash players when file loaded but no total length --- actionscript/happyworm/jPlayer/JplayerMp3.as | 2 ++ actionscript/happyworm/jPlayer/JplayerMp4.as | 2 ++ actionscript/happyworm/jPlayer/JplayerRtmp.as | 7 ++++--- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/actionscript/happyworm/jPlayer/JplayerMp3.as b/actionscript/happyworm/jPlayer/JplayerMp3.as index 1e6cf162..57a02781 100644 --- a/actionscript/happyworm/jPlayer/JplayerMp3.as +++ b/actionscript/happyworm/jPlayer/JplayerMp3.as @@ -337,6 +337,8 @@ package happyworm.jPlayer { public function getLoadRatio():Number { if((myStatus.isLoading || myStatus.isLoaded) && mySound.bytesTotal > 0) { return mySound.bytesLoaded / mySound.bytesTotal; + } else if (myStatus.isLoaded && mySound.bytesLoaded > 0) { + return 1; } else { return 0; } diff --git a/actionscript/happyworm/jPlayer/JplayerMp4.as b/actionscript/happyworm/jPlayer/JplayerMp4.as index 63bcfb66..b293dfaa 100644 --- a/actionscript/happyworm/jPlayer/JplayerMp4.as +++ b/actionscript/happyworm/jPlayer/JplayerMp4.as @@ -343,6 +343,8 @@ package happyworm.jPlayer { public function getLoadRatio():Number { if((myStatus.isLoading || myStatus.isLoaded) && myStream.bytesTotal > 0) { return myStream.bytesLoaded / myStream.bytesTotal; + } else if (myStatus.isLoaded && myStream.bytesLoaded > 0) { + return 1; } else { return 0; } diff --git a/actionscript/happyworm/jPlayer/JplayerRtmp.as b/actionscript/happyworm/jPlayer/JplayerRtmp.as index 1f8505aa..ed4b1031 100644 --- a/actionscript/happyworm/jPlayer/JplayerRtmp.as +++ b/actionscript/happyworm/jPlayer/JplayerRtmp.as @@ -871,10 +871,11 @@ package happyworm.jPlayer return 1; /*trace("LoadRatio:"+myStream.bytesLoaded, myStream.bytesTotal); if((myStatus.isLoading || myStatus.isLoaded) && myStream.bytesTotal > 0) { - - return myStream.bytesLoaded / myStream.bytesTotal; + return myStream.bytesLoaded / myStream.bytesTotal; + } else if (myStatus.isLoaded && myStream.bytesLoaded > 0) { + return 1; } else { - return 0; + return 0; } */ From 1e4adf6daa5eff33458ff53c122b3a0bc5d2ed51 Mon Sep 17 00:00:00 2001 From: Mark Panaghiston Date: Tue, 14 Oct 2014 19:35:19 +0100 Subject: [PATCH 046/187] Moved existing files into new repo structure. --- {jquery.jplayer => js/jplayer}/Jplayer.swf | Bin {actionscript => src/actionscript}/Jplayer.as | 0 {actionscript => src/actionscript}/Jplayer.fla | Bin .../happyworm/jPlayer/ConnectManager.as | 0 .../actionscript}/happyworm/jPlayer/JplayerEvent.as | 0 .../actionscript}/happyworm/jPlayer/JplayerMp3.as | 0 .../actionscript}/happyworm/jPlayer/JplayerMp4.as | 0 .../actionscript}/happyworm/jPlayer/JplayerRtmp.as | 0 .../happyworm/jPlayer/JplayerStatus.as | 0 {add-on => src/javascript}/jplayer.playlist.js | 0 .../javascript}/jquery.jplayer.inspector.js | 0 .../javascript}/jquery.jplayer.js | 0 .../player => src/javascript}/popcorn.jplayer.js | 0 13 files changed, 0 insertions(+), 0 deletions(-) rename {jquery.jplayer => js/jplayer}/Jplayer.swf (100%) rename {actionscript => src/actionscript}/Jplayer.as (100%) rename {actionscript => src/actionscript}/Jplayer.fla (100%) rename {actionscript => src/actionscript}/happyworm/jPlayer/ConnectManager.as (100%) rename {actionscript => src/actionscript}/happyworm/jPlayer/JplayerEvent.as (100%) rename {actionscript => src/actionscript}/happyworm/jPlayer/JplayerMp3.as (100%) rename {actionscript => src/actionscript}/happyworm/jPlayer/JplayerMp4.as (100%) rename {actionscript => src/actionscript}/happyworm/jPlayer/JplayerRtmp.as (100%) rename {actionscript => src/actionscript}/happyworm/jPlayer/JplayerStatus.as (100%) rename {add-on => src/javascript}/jplayer.playlist.js (100%) rename {add-on => src/javascript}/jquery.jplayer.inspector.js (100%) rename {jquery.jplayer => src/javascript}/jquery.jplayer.js (100%) rename {popcorn/player => src/javascript}/popcorn.jplayer.js (100%) diff --git a/jquery.jplayer/Jplayer.swf b/js/jplayer/Jplayer.swf similarity index 100% rename from jquery.jplayer/Jplayer.swf rename to js/jplayer/Jplayer.swf diff --git a/actionscript/Jplayer.as b/src/actionscript/Jplayer.as similarity index 100% rename from actionscript/Jplayer.as rename to src/actionscript/Jplayer.as diff --git a/actionscript/Jplayer.fla b/src/actionscript/Jplayer.fla similarity index 100% rename from actionscript/Jplayer.fla rename to src/actionscript/Jplayer.fla diff --git a/actionscript/happyworm/jPlayer/ConnectManager.as b/src/actionscript/happyworm/jPlayer/ConnectManager.as similarity index 100% rename from actionscript/happyworm/jPlayer/ConnectManager.as rename to src/actionscript/happyworm/jPlayer/ConnectManager.as diff --git a/actionscript/happyworm/jPlayer/JplayerEvent.as b/src/actionscript/happyworm/jPlayer/JplayerEvent.as similarity index 100% rename from actionscript/happyworm/jPlayer/JplayerEvent.as rename to src/actionscript/happyworm/jPlayer/JplayerEvent.as diff --git a/actionscript/happyworm/jPlayer/JplayerMp3.as b/src/actionscript/happyworm/jPlayer/JplayerMp3.as similarity index 100% rename from actionscript/happyworm/jPlayer/JplayerMp3.as rename to src/actionscript/happyworm/jPlayer/JplayerMp3.as diff --git a/actionscript/happyworm/jPlayer/JplayerMp4.as b/src/actionscript/happyworm/jPlayer/JplayerMp4.as similarity index 100% rename from actionscript/happyworm/jPlayer/JplayerMp4.as rename to src/actionscript/happyworm/jPlayer/JplayerMp4.as diff --git a/actionscript/happyworm/jPlayer/JplayerRtmp.as b/src/actionscript/happyworm/jPlayer/JplayerRtmp.as similarity index 100% rename from actionscript/happyworm/jPlayer/JplayerRtmp.as rename to src/actionscript/happyworm/jPlayer/JplayerRtmp.as diff --git a/actionscript/happyworm/jPlayer/JplayerStatus.as b/src/actionscript/happyworm/jPlayer/JplayerStatus.as similarity index 100% rename from actionscript/happyworm/jPlayer/JplayerStatus.as rename to src/actionscript/happyworm/jPlayer/JplayerStatus.as diff --git a/add-on/jplayer.playlist.js b/src/javascript/jplayer.playlist.js similarity index 100% rename from add-on/jplayer.playlist.js rename to src/javascript/jplayer.playlist.js diff --git a/add-on/jquery.jplayer.inspector.js b/src/javascript/jquery.jplayer.inspector.js similarity index 100% rename from add-on/jquery.jplayer.inspector.js rename to src/javascript/jquery.jplayer.inspector.js diff --git a/jquery.jplayer/jquery.jplayer.js b/src/javascript/jquery.jplayer.js similarity index 100% rename from jquery.jplayer/jquery.jplayer.js rename to src/javascript/jquery.jplayer.js diff --git a/popcorn/player/popcorn.jplayer.js b/src/javascript/popcorn.jplayer.js similarity index 100% rename from popcorn/player/popcorn.jplayer.js rename to src/javascript/popcorn.jplayer.js From ee1c24f6178f0747609461e7cb928927f3b534cd Mon Sep 17 00:00:00 2001 From: Mark Panaghiston Date: Tue, 14 Oct 2014 20:27:20 +0100 Subject: [PATCH 047/187] Added any remote libraries to the lib folder. --- lib/circle.player.js | 243 +++ lib/jquery.grab.js | 201 +++ lib/jquery.transform2d.js | 551 +++++++ lib/mod.csstransforms.min.js | 2 + lib/popcorn.ie8.js | 450 ++++++ lib/popcorn.js | 2690 ++++++++++++++++++++++++++++++++++ lib/popcorn.player.js | 437 ++++++ lib/popcorn.subtitle.js | 143 ++ 8 files changed, 4717 insertions(+) create mode 100644 lib/circle.player.js create mode 100644 lib/jquery.grab.js create mode 100644 lib/jquery.transform2d.js create mode 100644 lib/mod.csstransforms.min.js create mode 100644 lib/popcorn.ie8.js create mode 100644 lib/popcorn.js create mode 100644 lib/popcorn.player.js create mode 100644 lib/popcorn.subtitle.js diff --git a/lib/circle.player.js b/lib/circle.player.js new file mode 100644 index 00000000..c19b0b87 --- /dev/null +++ b/lib/circle.player.js @@ -0,0 +1,243 @@ +/* + * CirclePlayer for the jPlayer Plugin (jQuery) + * http://www.jplayer.org + * + * Copyright (c) 2009 - 2012 Happyworm Ltd + * Dual licensed under the MIT and GPL licenses. + * - http://www.opensource.org/licenses/mit-license.php + * - http://www.gnu.org/copyleft/gpl.html + * + * Version: 1.0.1 (jPlayer 2.1.0+) + * Date: 30th May 2011 + * + * Author: Mark J Panaghiston @thepag + * + * CirclePlayer prototype developed by: + * Mark Boas @maboa + * Silvia Benvenuti @aulentina + * Jussi Kalliokoski @quinirill + * + * Inspired by : + * Neway @imneway http://imneway.net/ http://forrst.com/posts/Untitled-CPt + * and + * Liam McKay @liammckay http://dribbble.com/shots/50882-Purple-Play-Pause + * + * Standing on the shoulders of : + * John Resig @jresig + * Mark Panaghiston @thepag + * Louis-Rémi Babé @Louis_Remi + */ + + +var CirclePlayer = function(jPlayerSelector, media, options) { + var self = this, + + defaults = { + // solution: "flash, html", // For testing Flash with CSS3 + supplied: "m4a, oga", + // Android 2.3 corrupts media element if preload:"none" is used. + // preload: "none", // No point preloading metadata since no times are displayed. It helps keep the buffer state correct too. + cssSelectorAncestor: "#cp_container_1", + cssSelector: { + play: ".cp-play", + pause: ".cp-pause" + } + }, + + cssSelector = { + bufferHolder: ".cp-buffer-holder", + buffer1: ".cp-buffer-1", + buffer2: ".cp-buffer-2", + progressHolder: ".cp-progress-holder", + progress1: ".cp-progress-1", + progress2: ".cp-progress-2", + circleControl: ".cp-circle-control" + }; + + this.cssClass = { + gt50: "cp-gt50", + fallback: "cp-fallback" + }; + + this.spritePitch = 104; + this.spriteRatio = 0.24; // Number of steps / 100 + + this.player = $(jPlayerSelector); + this.media = $.extend({}, media); + this.options = $.extend(true, {}, defaults, options); // Deep copy + + this.cssTransforms = Modernizr.csstransforms; + this.audio = {}; + this.dragging = false; // Indicates if the progressbar is being 'dragged'. + + this.eventNamespace = ".CirclePlayer"; // So the events can easily be removed in destroy. + + this.jq = {}; + $.each(cssSelector, function(entity, cssSel) { + self.jq[entity] = $(self.options.cssSelectorAncestor + " " + cssSel); + }); + + this._initSolution(); + this._initPlayer(); +}; + +CirclePlayer.prototype = { + _createHtml: function() { + }, + _initPlayer: function() { + var self = this; + this.player.jPlayer(this.options); + + this.player.bind($.jPlayer.event.ready + this.eventNamespace, function(event) { + if(event.jPlayer.html.used && event.jPlayer.html.audio.available) { + self.audio = $(this).data("jPlayer").htmlElement.audio; + } + $(this).jPlayer("setMedia", self.media); + self._initCircleControl(); + }); + + this.player.bind($.jPlayer.event.play + this.eventNamespace, function(event) { + $(this).jPlayer("pauseOthers"); + }); + + // This event fired as play time increments + this.player.bind($.jPlayer.event.timeupdate + this.eventNamespace, function(event) { + if (!self.dragging) { + self._timeupdate(event.jPlayer.status.currentPercentAbsolute); + } + }); + + // This event fired as buffered time increments + this.player.bind($.jPlayer.event.progress + this.eventNamespace, function(event) { + var percent = 0; + if((typeof self.audio.buffered === "object") && (self.audio.buffered.length > 0)) { + if(self.audio.duration > 0) { + var bufferTime = 0; + for(var i = 0; i < self.audio.buffered.length; i++) { + bufferTime += self.audio.buffered.end(i) - self.audio.buffered.start(i); + // console.log(i + " | start = " + self.audio.buffered.start(i) + " | end = " + self.audio.buffered.end(i) + " | bufferTime = " + bufferTime + " | duration = " + self.audio.duration); + } + percent = 100 * bufferTime / self.audio.duration; + } // else the Metadata has not been read yet. + // console.log("percent = " + percent); + } else { // Fallback if buffered not supported + // percent = event.jPlayer.status.seekPercent; + percent = 0; // Cleans up the inital conditions on all browsers, since seekPercent defaults to 100 when object is undefined. + } + self._progress(percent); // Problem here at initial condition. Due to the Opera clause above of buffered.length > 0 above... Removing it means Opera's white buffer ring never shows like with polyfill. + // Firefox 4 does not always give the final progress event when buffered = 100% + }); + + this.player.bind($.jPlayer.event.ended + this.eventNamespace, function(event) { + self._resetSolution(); + }); + }, + _initSolution: function() { + if (this.cssTransforms) { + this.jq.progressHolder.show(); + this.jq.bufferHolder.show(); + } + else { + this.jq.progressHolder.addClass(this.cssClass.gt50).show(); + this.jq.progress1.addClass(this.cssClass.fallback); + this.jq.progress2.hide(); + this.jq.bufferHolder.hide(); + } + this._resetSolution(); + }, + _resetSolution: function() { + if (this.cssTransforms) { + this.jq.progressHolder.removeClass(this.cssClass.gt50); + this.jq.progress1.css({'transform': 'rotate(0deg)'}); + this.jq.progress2.css({'transform': 'rotate(0deg)'}).hide(); + } + else { + this.jq.progress1.css('background-position', '0 ' + this.spritePitch + 'px'); + } + }, + _initCircleControl: function() { + var self = this; + this.jq.circleControl.grab({ + onstart: function(){ + self.dragging = true; + }, onmove: function(event){ + var pc = self._getArcPercent(event.position.x, event.position.y); + self.player.jPlayer("playHead", pc).jPlayer("play"); + self._timeupdate(pc); + }, onfinish: function(event){ + self.dragging = false; + var pc = self._getArcPercent(event.position.x, event.position.y); + self.player.jPlayer("playHead", pc).jPlayer("play"); + } + }); + }, + _timeupdate: function(percent) { + var degs = percent * 3.6+"deg"; + + var spriteOffset = (Math.floor((Math.round(percent))*this.spriteRatio)-1)*-this.spritePitch; + + if (percent <= 50) { + if (this.cssTransforms) { + this.jq.progressHolder.removeClass(this.cssClass.gt50); + this.jq.progress1.css({'transform': 'rotate(' + degs + ')'}); + this.jq.progress2.hide(); + } else { // fall back + this.jq.progress1.css('background-position', '0 '+spriteOffset+'px'); + } + } else if (percent <= 100) { + if (this.cssTransforms) { + this.jq.progressHolder.addClass(this.cssClass.gt50); + this.jq.progress1.css({'transform': 'rotate(180deg)'}); + this.jq.progress2.css({'transform': 'rotate(' + degs + ')'}); + this.jq.progress2.show(); + } else { // fall back + this.jq.progress1.css('background-position', '0 '+spriteOffset+'px'); + } + } + }, + _progress: function(percent) { + var degs = percent * 3.6+"deg"; + + if (this.cssTransforms) { + if (percent <= 50) { + this.jq.bufferHolder.removeClass(this.cssClass.gt50); + this.jq.buffer1.css({'transform': 'rotate(' + degs + ')'}); + this.jq.buffer2.hide(); + } else if (percent <= 100) { + this.jq.bufferHolder.addClass(this.cssClass.gt50); + this.jq.buffer1.css({'transform': 'rotate(180deg)'}); + this.jq.buffer2.show(); + this.jq.buffer2.css({'transform': 'rotate(' + degs + ')'}); + } + } + }, + _getArcPercent: function(pageX, pageY) { + var offset = this.jq.circleControl.offset(), + x = pageX - offset.left - this.jq.circleControl.width()/2, + y = pageY - offset.top - this.jq.circleControl.height()/2, + theta = Math.atan2(y,x); + + if (theta > -1 * Math.PI && theta < -0.5 * Math.PI) { + theta = 2 * Math.PI + theta; + } + + // theta is now value between -0.5PI and 1.5PI + // ready to be normalized and applied + + return (theta + Math.PI / 2) / 2 * Math.PI * 10; + }, + setMedia: function(media) { + this.media = $.extend({}, media); + this.player.jPlayer("setMedia", this.media); + }, + play: function(time) { + this.player.jPlayer("play", time); + }, + pause: function(time) { + this.player.jPlayer("pause", time); + }, + destroy: function() { + this.player.unbind(this.eventNamespace); + this.player.jPlayer("destroy"); + } +}; diff --git a/lib/jquery.grab.js b/lib/jquery.grab.js new file mode 100644 index 00000000..d7318991 --- /dev/null +++ b/lib/jquery.grab.js @@ -0,0 +1,201 @@ +/* +jQuery grab +https://github.com/jussi-kalliokoski/jQuery.grab +Ported from Jin.js::gestures +https://github.com/jussi-kalliokoski/jin.js/ +Created by Jussi Kalliokoski +Licensed under MIT License. + +Includes fix for IE +*/ + + +(function($){ + var extend = $.extend, + mousedown = 'mousedown', + mousemove = 'mousemove', + mouseup = 'mouseup', + touchstart = 'touchstart', + touchmove = 'touchmove', + touchend = 'touchend', + touchcancel = 'touchcancel'; + + function unbind(elem, type, func){ + if (type.substr(0,5) !== 'touch'){ // A temporary fix for IE8 data passing problem in Jin. + return $(elem).unbind(type, func); + } + var fnc, i; + for (i=0; i1 ? val[1] : val[0]; + break; + + case _skew+"X": + curr[2] = Math.tan(toRadian(val)); + break; + + case _skew+"Y": + curr[1] = Math.tan(toRadian(val)); + break; + + case _matrix: + val = val.split(","); + curr[0] = val[0]; + curr[1] = val[1]; + curr[2] = val[2]; + curr[3] = val[3]; + curr[4] = parseInt(val[4], 10); + curr[5] = parseInt(val[5], 10); + break; + } + + // Matrix product (array in column-major order) + rslt[0] = prev[0] * curr[0] + prev[2] * curr[1]; + rslt[1] = prev[1] * curr[0] + prev[3] * curr[1]; + rslt[2] = prev[0] * curr[2] + prev[2] * curr[3]; + rslt[3] = prev[1] * curr[2] + prev[3] * curr[3]; + rslt[4] = prev[0] * curr[4] + prev[2] * curr[5] + prev[4]; + rslt[5] = prev[1] * curr[4] + prev[3] * curr[5] + prev[5]; + + prev = [rslt[0],rslt[1],rslt[2],rslt[3],rslt[4],rslt[5]]; + } + return rslt; +} + +// turns a matrix into its rotate, scale and skew components +// algorithm from http://hg.mozilla.org/mozilla-central/file/7cb3e9795d04/layout/style/nsStyleAnimation.cpp +function unmatrix(matrix) { + var + scaleX + , scaleY + , skew + , A = matrix[0] + , B = matrix[1] + , C = matrix[2] + , D = matrix[3] + ; + + // Make sure matrix is not singular + if ( A * D - B * C ) { + // step (3) + scaleX = Math.sqrt( A * A + B * B ); + A /= scaleX; + B /= scaleX; + // step (4) + skew = A * C + B * D; + C -= A * skew; + D -= B * skew; + // step (5) + scaleY = Math.sqrt( C * C + D * D ); + C /= scaleY; + D /= scaleY; + skew /= scaleY; + // step (6) + if ( A * D < B * C ) { + A = -A; + B = -B; + skew = -skew; + scaleX = -scaleX; + } + + // matrix is singular and cannot be interpolated + } else { + // In this case the elem shouldn't be rendered, hence scale == 0 + scaleX = scaleY = skew = 0; + } + + // The recomposition order is very important + // see http://hg.mozilla.org/mozilla-central/file/7cb3e9795d04/layout/style/nsStyleAnimation.cpp#l971 + return [ + [_translate, [+matrix[4], +matrix[5]]], + [_rotate, Math.atan2(B, A)], + [_skew + "X", Math.atan(skew)], + [_scale, [scaleX, scaleY]] + ]; +} + +// build the list of transform functions to interpolate +// use the algorithm described at http://dev.w3.org/csswg/css3-2d-transforms/#animation +function interpolationList( start, end ) { + var list = { + start: [], + end: [] + }, + i = -1, l, + currStart, currEnd, currType; + + // get rid of affine transform matrix + ( start == "none" || isAffine( start ) ) && ( start = "" ); + ( end == "none" || isAffine( end ) ) && ( end = "" ); + + // if end starts with the current computed style, this is a relative animation + // store computed style as the origin, remove it from start and end + if ( start && end && !end.indexOf("matrix") && toArray( start ).join() == toArray( end.split(")")[0] ).join() ) { + list.origin = start; + start = ""; + end = end.slice( end.indexOf(")") +1 ); + } + + if ( !start && !end ) { return; } + + // start or end are affine, or list of transform functions are identical + // => functions will be interpolated individually + if ( !start || !end || functionList(start) == functionList(end) ) { + + start && ( start = start.split(")") ) && ( l = start.length ); + end && ( end = end.split(")") ) && ( l = end.length ); + + while ( ++i < l-1 ) { + start[i] && ( currStart = start[i].split("(") ); + end[i] && ( currEnd = end[i].split("(") ); + currType = $.trim( ( currStart || currEnd )[0] ); + + append( list.start, parseFunction( currType, currStart ? currStart[1] : 0 ) ); + append( list.end, parseFunction( currType, currEnd ? currEnd[1] : 0 ) ); + } + + // otherwise, functions will be composed to a single matrix + } else { + list.start = unmatrix(matrix(start)); + list.end = unmatrix(matrix(end)) + } + + return list; +} + +function parseFunction( type, value ) { + var + // default value is 1 for scale, 0 otherwise + defaultValue = +(!type.indexOf(_scale)), + scaleX, + // remove X/Y from scaleX/Y & translateX/Y, not from skew + cat = type.replace( /e[XY]/, "e" ); + + switch ( type ) { + case _translate+"Y": + case _scale+"Y": + + value = [ + defaultValue, + value ? + parseFloat( value ): + defaultValue + ]; + break; + + case _translate+"X": + case _translate: + case _scale+"X": + scaleX = 1; + case _scale: + + value = value ? + ( value = value.split(",") ) && [ + parseFloat( value[0] ), + parseFloat( value.length>1 ? value[1] : type == _scale ? scaleX || value[0] : defaultValue+"" ) + ]: + [defaultValue, defaultValue]; + break; + + case _skew+"X": + case _skew+"Y": + case _rotate: + value = value ? toRadian( value ) : 0; + break; + + case _matrix: + return unmatrix( value ? toArray(value) : [1,0,0,1,0,0] ); + break; + } + + return [[ cat, value ]]; +} + +function isAffine( matrix ) { + return rAffine.test(matrix); +} + +function functionList( transform ) { + return transform.replace(/(?:\([^)]*\))|\s/g, ""); +} + +function append( arr1, arr2, value ) { + while ( value = arr2.shift() ) { + arr1.push( value ); + } +} + +// converts an angle string in any unit to a radian Float +function toRadian(value) { + return ~value.indexOf("deg") ? + parseInt(value,10) * (Math.PI * 2 / 360): + ~value.indexOf("grad") ? + parseInt(value,10) * (Math.PI/200): + parseFloat(value); +} + +// Converts "matrix(A,B,C,D,X,Y)" to [A,B,C,D,X,Y] +function toArray(matrix) { + // remove the unit of X and Y for Firefox + matrix = /([^,]*),([^,]*),([^,]*),([^,]*),([^,p]*)(?:px)?,([^)p]*)(?:px)?/.exec(matrix); + return [matrix[1], matrix[2], matrix[3], matrix[4], matrix[5], matrix[6]]; +} + +$.transform = { + centerOrigin: "margin" +}; + +})( jQuery, window, document, Math ); diff --git a/lib/mod.csstransforms.min.js b/lib/mod.csstransforms.min.js new file mode 100644 index 00000000..6386d5bf --- /dev/null +++ b/lib/mod.csstransforms.min.js @@ -0,0 +1,2 @@ +/* Modernizr custom build of 1.7pre: csstransforms */ +window.Modernizr=function(a,b,c){function G(){}function F(a,b){var c=a.charAt(0).toUpperCase()+a.substr(1),d=(a+" "+p.join(c+" ")+c).split(" ");return!!E(d,b)}function E(a,b){for(var d in a)if(k[a[d]]!==c&&(!b||b(a[d],j)))return!0}function D(a,b){return(""+a).indexOf(b)!==-1}function C(a,b){return typeof a===b}function B(a,b){return A(o.join(a+";")+(b||""))}function A(a){k.cssText=a}var d="1.7pre",e={},f=!0,g=b.documentElement,h=b.head||b.getElementsByTagName("head")[0],i="modernizr",j=b.createElement(i),k=j.style,l=b.createElement("input"),m=":)",n=Object.prototype.toString,o=" -webkit- -moz- -o- -ms- -khtml- ".split(" "),p="Webkit Moz O ms Khtml".split(" "),q={svg:"http://www.w3.org/2000/svg"},r={},s={},t={},u=[],v,w=function(a){var c=b.createElement("style"),d=b.createElement("div"),e;c.textContent=a+"{#modernizr{height:3px}}",h.appendChild(c),d.id="modernizr",g.appendChild(d),e=d.offsetHeight===3,c.parentNode.removeChild(c),d.parentNode.removeChild(d);return!!e},x=function(){function d(d,e){e=e||b.createElement(a[d]||"div");var f=(d="on"+d)in e;f||(e.setAttribute||(e=b.createElement("div")),e.setAttribute&&e.removeAttribute&&(e.setAttribute(d,""),f=C(e[d],"function"),C(e[d],c)||(e[d]=c),e.removeAttribute(d))),e=null;return f}var a={select:"input",change:"input",submit:"form",reset:"form",error:"img",load:"img",abort:"img"};return d}(),y=({}).hasOwnProperty,z;C(y,c)||C(y.call,c)?z=function(a,b){return b in a&&C(a.constructor.prototype[b],c)}:z=function(a,b){return y.call(a,b)},r.csstransforms=function(){return!!E(["transformProperty","WebkitTransform","MozTransform","OTransform","msTransform"])};for(var H in r)z(r,H)&&(v=H.toLowerCase(),e[v]=r[H](),u.push((e[v]?"":"no-")+v));e.input||G(),e.crosswindowmessaging=e.postmessage,e.historymanagement=e.history,e.addTest=function(a,b){a=a.toLowerCase();if(!e[a]){b=!!b(),g.className+=" "+(b?"":"no-")+a,e[a]=b;return e}},A(""),j=l=null,e._enableHTML5=f,e._version=d,g.className=g.className.replace(/\bno-js\b/,"")+" js "+u.join(" ");return e}(this,this.document) \ No newline at end of file diff --git a/lib/popcorn.ie8.js b/lib/popcorn.ie8.js new file mode 100644 index 00000000..c215e59a --- /dev/null +++ b/lib/popcorn.ie8.js @@ -0,0 +1,450 @@ +(function() { + + if ( !document.addEventListener && !document.removeEventListener && !document.dispatchEvent ) { + var events = {}; + + var addEventListener = function( eventName, callBack ) { + + eventName = ( eventName === "DOMContentLoaded" ) ? "readystatechange" : eventName; + + if ( Event[ eventName.toUpperCase() ] || eventName === "readystatechange" ) { + document.attachEvent( "on" + eventName, callBack ); + return; + } + + if ( !events[ eventName ] ) { + events[ eventName ] = { + events: [], + queue: [], + active: false + }; + } + + if ( events[ eventName ].active ) { + events[ eventName ].queue.push( callBack ); + } else { + events[ eventName ].events.push( callBack ); + } + }; + + var removeEventListener = function( eventName, callBack ) { + + eventName = ( eventName === "DOMContentLoaded" ) ? "readystatechange" : eventName; + + var i = 0, + listeners = events[ eventName ]; + + if ( Event[ eventName.toUpperCase() ] || eventName === "readystatechange" ) { + document.detachEvent( "on" + eventName, callBack ); + return; + } + + if ( !listeners ) { + return; + } + + for ( i = listeners.events.length - 1; i >= 0; i-- ) { + if ( callBack === listeners.events[ i ] ) { + delete listeners.events[ i ]; + } + } + + for ( i = listeners.queue.length - 1; i >= 0; i-- ) { + if ( callBack === listeners.queue[ i ] ) { + delete listeners.queue[ i ]; + } + } + }; + + var dispatchEvent = function( eventObject ) { + var evt, + self = this, + eventInterface, + listeners, + eventName = eventObject.type, + queuedListener; + + // A string was passed, create event object + if ( !eventName ) { + + eventName = eventObject; + eventInterface = Popcorn.events.getInterface( eventName ); + + if ( eventInterface ) { + + evt = document.createEvent( eventInterface ); + evt.initCustomEvent( eventName, true, true, window, 1 ); + } + } + + listeners = events[ eventName ]; + + if ( listeners ) { + listeners.active = true; + + for ( var i = 0; i < listeners.events.length; i++ ) { + if ( listeners.events[ i ] ) { + listeners.events[ i ].call( self, evt, self ); + } + } + + if ( listeners.queue.length ) { + while ( listeners.queue.length ) { + queuedListener = listeners.queue.shift(); + + if ( queuedListener ) { + listeners.events.push( queuedListener ); + } + } + } + + listeners.active = false; + + listeners.events.forEach(function( listener ) { + if ( !listener ) { + listeners.events.splice( listeners.events.indexOf( listener ), 1 ); + } + }); + + listeners.queue.forEach(function( listener ) { + if ( !listener ) { + listeners.queue.splice( listeners.queue.indexOf( listener ), 1 ); + } + }); + } + }; + + document.addEventListener = addEventListener; + document.removeEventListener = removeEventListener; + document.dispatchEvent = dispatchEvent; + + } + + if ( !Event.prototype.preventDefault ) { + Event.prototype.preventDefault = function() { + this.returnValue = false; + }; + } + if ( !Event.prototype.stopPropagation ) { + Event.prototype.stopPropagation = function() { + this.cancelBubble = true; + }; + } + + window.addEventListener = window.addEventListener || function( event, callBack ) { + + event = "on" + event; + + window.attachEvent( event, callBack ); + }; + + window.removeEventListener = window.removeEventListener || function( event, callBack ) { + + event = "on" + event; + + window.detachEvent( event, callBack ); + }; + + HTMLScriptElement.prototype.addEventListener = HTMLScriptElement.prototype.addEventListener || function( event, callBack ) { + + event = ( event === "load" ) ? "onreadystatechange" : "on" + event; + + if( event === "onreadystatechange" ){ + callBack.readyStateCheck = callBack.readyStateCheck || function( e ){ + + if( self.readyState === "loaded" ){ + callBack( e ); + } + }; + } + + this.attachEvent( event, ( callBack.readyStateCheck || callBack ) ); + }; + + HTMLScriptElement.prototype.removeEventListener = HTMLScriptElement.prototype.removeEventListener || function( event, callBack ) { + + event = ( event === "load" ) ? "onreadystatechange" : "on" + event; + + this.detachEvent( event, ( callBack.readyStateCheck || callBack ) ); + }; + + document.createEvent = document.createEvent || function ( type ) { + + return { + type : null, + target : null, + currentTarget : null, + cancelable : false, + detail: false, + bubbles : false, + initEvent : function (type, bubbles, cancelable) { + this.type = type; + }, + initCustomEvent: function(type, bubbles, cancelable, detail) { + this.type = type; + this.detail = detail; + }, + stopPropagation : function () {}, + stopImmediatePropagation : function () {} + } + }; + + Array.prototype.forEach = Array.prototype.forEach || function( fn, context ) { + + var obj = this, + hasOwn = Object.prototype.hasOwnProperty; + + if ( !obj || !fn ) { + return {}; + } + + context = context || this; + + var key, len; + + for ( key in obj ) { + if ( hasOwn.call( obj, key ) ) { + fn.call( context, obj[ key ], key, obj ); + } + } + return obj; + }; + + // Production steps of ECMA-262, Edition 5, 15.4.4.19 + // Reference: http://es5.github.com/#x15.4.4.19 + if ( !Array.prototype.map ) { + + Array.prototype.map = function( callback, thisArg ) { + + var T, A, k; + + if ( this == null ) { + throw new TypeError( "this is null or not defined" ); + } + + // 1. Let O be the result of calling ToObject passing the |this| value as the argument. + var O = Object( this ); + + // 2. Let lenValue be the result of calling the Get internal method of O with the argument "length". + // 3. Let len be ToUint32(lenValue). + var len = O.length >>> 0; + + // 4. If IsCallable(callback) is false, throw a TypeError exception. + // See: http://es5.github.com/#x9.11 + if ( {}.toString.call( callback ) != "[object Function]" ) { + throw new TypeError( callback + " is not a function" ); + } + + // 5. If thisArg was supplied, let T be thisArg; else let T be undefined. + if ( thisArg ) { + T = thisArg; + } + + // 6. Let A be a new array created as if by the expression new Array(len) where Array is + // the standard built-in constructor with that name and len is the value of len. + A = new Array( len ); + + // 7. Let k be 0 + k = 0; + + // 8. Repeat, while k < len + while( k < len ) { + + var kValue, mappedValue; + + // a. Let Pk be ToString(k). + // This is implicit for LHS operands of the in operator + // b. Let kPresent be the result of calling the HasProperty internal method of O with argument Pk. + // This step can be combined with c + // c. If kPresent is true, then + if ( k in O ) { + + // i. Let kValue be the result of calling the Get internal method of O with argument Pk. + kValue = O[ k ]; + + // ii. Let mappedValue be the result of calling the Call internal method of callback + // with T as the this value and argument list containing kValue, k, and O. + mappedValue = callback.call( T, kValue, k, O ); + + // iii. Call the DefineOwnProperty internal method of A with arguments + // Pk, Property Descriptor {Value: mappedValue, Writable: true, Enumerable: true, Configurable: true}, + // and false. + + // In browsers that support Object.defineProperty, use the following: + // Object.defineProperty(A, Pk, { value: mappedValue, writable: true, enumerable: true, configurable: true }); + + // For best browser support, use the following: + A[ k ] = mappedValue; + } + // d. Increase k by 1. + k++; + } + + // 9. return A + return A; + }; + } + + if ( !Array.prototype.indexOf ) { + + Array.prototype.indexOf = function ( searchElement /*, fromIndex */ ) { + + if ( this == null) { + + throw new TypeError(); + } + + var t = Object( this ), + len = t.length >>> 0; + + if ( len === 0 ) { + + return -1; + } + + var n = 0; + + if ( arguments.length > 0 ) { + + n = Number( arguments[ 1 ] ); + + if ( n != n ) { // shortcut for verifying if it's NaN + + n = 0; + } else if ( n != 0 && n != Infinity && n != -Infinity ) { + + n = ( n > 0 || -1 ) * Math.floor( Math.abs( n ) ); + } + } + + if ( n >= len ) { + return -1; + } + + var k = n >= 0 ? n : Math.max( len - Math.abs( n ), 0 ); + + for (; k < len; k++ ) { + + if ( k in t && t[ k ] === searchElement ) { + + return k; + } + } + + return -1; + } + } + + if ( typeof String.prototype.trim !== "function" ) { + + String.prototype.trim = function() { + return this.replace(/^\s+|\s+$/g, ""); + }; + } + + // From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys + if (!Object.keys) { + Object.keys = (function () { + 'use strict'; + var hasOwnProperty = Object.prototype.hasOwnProperty, + hasDontEnumBug = !({toString: null}).propertyIsEnumerable('toString'), + dontEnums = [ + 'toString', + 'toLocaleString', + 'valueOf', + 'hasOwnProperty', + 'isPrototypeOf', + 'propertyIsEnumerable', + 'constructor' + ], + dontEnumsLength = dontEnums.length; + + return function (obj) { + if (typeof obj !== 'object' && (typeof obj !== 'function' || obj === null)) { + throw new TypeError('Object.keys called on non-object'); + } + + var result = [], prop, i; + + for (prop in obj) { + if (hasOwnProperty.call(obj, prop)) { + result.push(prop); + } + } + + if (hasDontEnumBug) { + for (i = 0; i < dontEnumsLength; i++) { + if (hasOwnProperty.call(obj, dontEnums[i])) { + result.push(dontEnums[i]); + } + } + } + return result; + }; + }()); + } + + if ( !Object.defineProperties ) { + Object.defineProperties = function(obj, properties) { + function convertToDescriptor(desc) { + function hasProperty(obj, prop) { + return Object.prototype.hasOwnProperty.call(obj, prop); + } + + function isCallable(v) { + // NB: modify as necessary if other values than functions are callable. + return typeof v === "function"; + } + + if (typeof desc !== "object" || desc === null) + throw new TypeError("bad desc"); + + var d = {}; + + if (hasProperty(desc, "enumerable")) + d.enumerable = !!obj.enumerable; + if (hasProperty(desc, "configurable")) + d.configurable = !!desc.configurable; + if (hasProperty(desc, "value")) + d.value = obj.value; + if (hasProperty(desc, "writable")) + d.writable = !!desc.writable; + if ( hasProperty(desc, "get") ) { + var g = desc.get; + + if (!isCallable(g) && g !== "undefined") + throw new TypeError("bad get"); + d.get = g; + } + if ( hasProperty(desc, "set") ) { + var s = desc.set; + if (!isCallable(s) && s !== "undefined") + throw new TypeError("bad set"); + d.set = s; + } + + if (("get" in d || "set" in d) && ("value" in d || "writable" in d)) + throw new TypeError("identity-confused descriptor"); + + return d; + } + + if (typeof obj !== "object" || obj === null) + throw new TypeError("bad obj"); + + properties = Object(properties); + + var keys = Object.keys(properties); + var descs = []; + + for (var i = 0; i < keys.length; i++) + descs.push([keys[i], convertToDescriptor(properties[keys[i]])]); + + for (var i = 0; i < descs.length; i++) + Object.defineProperty(obj, descs[i][0], descs[i][1]); + + return obj; + }; + } + +})(); diff --git a/lib/popcorn.js b/lib/popcorn.js new file mode 100644 index 00000000..0f9eec69 --- /dev/null +++ b/lib/popcorn.js @@ -0,0 +1,2690 @@ +(function(global, document) { + + // Popcorn.js does not support archaic browsers + if ( !document.addEventListener ) { + global.Popcorn = { + isSupported: false + }; + + var methods = ( "byId forEach extend effects error guid sizeOf isArray nop position disable enable destroy" + + "addTrackEvent removeTrackEvent getTrackEvents getTrackEvent getLastTrackEventId " + + "timeUpdate plugin removePlugin compose effect xhr getJSONP getScript" ).split(/\s+/); + + while ( methods.length ) { + global.Popcorn[ methods.shift() ] = function() {}; + } + return; + } + + var + + AP = Array.prototype, + OP = Object.prototype, + + forEach = AP.forEach, + slice = AP.slice, + hasOwn = OP.hasOwnProperty, + toString = OP.toString, + + // Copy global Popcorn (may not exist) + _Popcorn = global.Popcorn, + + // Ready fn cache + readyStack = [], + readyBound = false, + readyFired = false, + + // Non-public internal data object + internal = { + events: { + hash: {}, + apis: {} + } + }, + + // Non-public `requestAnimFrame` + // http://paulirish.com/2011/requestanimationframe-for-smart-animating/ + requestAnimFrame = (function(){ + return global.requestAnimationFrame || + global.webkitRequestAnimationFrame || + global.mozRequestAnimationFrame || + global.oRequestAnimationFrame || + global.msRequestAnimationFrame || + function( callback, element ) { + global.setTimeout( callback, 16 ); + }; + }()), + + // Non-public `getKeys`, return an object's keys as an array + getKeys = function( obj ) { + return Object.keys ? Object.keys( obj ) : (function( obj ) { + var item, + list = []; + + for ( item in obj ) { + if ( hasOwn.call( obj, item ) ) { + list.push( item ); + } + } + return list; + })( obj ); + }, + + Abstract = { + // [[Put]] props from dictionary onto |this| + // MUST BE CALLED FROM WITHIN A CONSTRUCTOR: + // Abstract.put.call( this, dictionary ); + put: function( dictionary ) { + // For each own property of src, let key be the property key + // and desc be the property descriptor of the property. + for ( var key in dictionary ) { + if ( dictionary.hasOwnProperty( key ) ) { + this[ key ] = dictionary[ key ]; + } + } + } + }, + + + // Declare constructor + // Returns an instance object. + Popcorn = function( entity, options ) { + // Return new Popcorn object + return new Popcorn.p.init( entity, options || null ); + }; + + // Popcorn API version, automatically inserted via build system. + Popcorn.version = "@VERSION"; + + // Boolean flag allowing a client to determine if Popcorn can be supported + Popcorn.isSupported = true; + + // Instance caching + Popcorn.instances = []; + + // Declare a shortcut (Popcorn.p) to and a definition of + // the new prototype for our Popcorn constructor + Popcorn.p = Popcorn.prototype = { + + init: function( entity, options ) { + + var matches, nodeName, + self = this; + + // Supports Popcorn(function () { /../ }) + // Originally proposed by Daniel Brooks + + if ( typeof entity === "function" ) { + + // If document ready has already fired + if ( document.readyState === "complete" ) { + + entity( document, Popcorn ); + + return; + } + // Add `entity` fn to ready stack + readyStack.push( entity ); + + // This process should happen once per page load + if ( !readyBound ) { + + // set readyBound flag + readyBound = true; + + var DOMContentLoaded = function() { + + readyFired = true; + + // Remove global DOM ready listener + document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + + // Execute all ready function in the stack + for ( var i = 0, readyStackLength = readyStack.length; i < readyStackLength; i++ ) { + + readyStack[ i ].call( document, Popcorn ); + + } + // GC readyStack + readyStack = null; + }; + + // Register global DOM ready listener + document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + } + + return; + } + + if ( typeof entity === "string" ) { + try { + matches = document.querySelector( entity ); + } catch( e ) { + throw new Error( "Popcorn.js Error: Invalid media element selector: " + entity ); + } + } + + // Get media element by id or object reference + this.media = matches || entity; + + // inner reference to this media element's nodeName string value + nodeName = ( this.media.nodeName && this.media.nodeName.toLowerCase() ) || "video"; + + // Create an audio or video element property reference + this[ nodeName ] = this.media; + + this.options = Popcorn.extend( {}, options ) || {}; + + // Resolve custom ID or default prefixed ID + this.id = this.options.id || Popcorn.guid( nodeName ); + + // Throw if an attempt is made to use an ID that already exists + if ( Popcorn.byId( this.id ) ) { + throw new Error( "Popcorn.js Error: Cannot use duplicate ID (" + this.id + ")" ); + } + + this.isDestroyed = false; + + this.data = { + + // data structure of all + running: { + cue: [] + }, + + // Executed by either timeupdate event or in rAF loop + timeUpdate: Popcorn.nop, + + // Allows disabling a plugin per instance + disabled: {}, + + // Stores DOM event queues by type + events: {}, + + // Stores Special event hooks data + hooks: {}, + + // Store track event history data + history: [], + + // Stores ad-hoc state related data] + state: { + volume: this.media.volume + }, + + // Store track event object references by trackId + trackRefs: {}, + + // Playback track event queues + trackEvents: new TrackEvents( this ) + }; + + // Register new instance + Popcorn.instances.push( this ); + + // function to fire when video is ready + var isReady = function() { + + // chrome bug: http://code.google.com/p/chromium/issues/detail?id=119598 + // it is possible the video's time is less than 0 + // this has the potential to call track events more than once, when they should not + // start: 0, end: 1 will start, end, start again, when it should just start + // just setting it to 0 if it is below 0 fixes this issue + if ( self.media.currentTime < 0 ) { + + self.media.currentTime = 0; + } + + self.media.removeEventListener( "loadedmetadata", isReady, false ); + + var duration, videoDurationPlus, + runningPlugins, runningPlugin, rpLength, rpNatives; + + // Adding padding to the front and end of the arrays + // this is so we do not fall off either end + duration = self.media.duration; + + // Check for no duration info (NaN) + videoDurationPlus = duration != duration ? Number.MAX_VALUE : duration + 1; + + Popcorn.addTrackEvent( self, { + start: videoDurationPlus, + end: videoDurationPlus + }); + + if ( !self.isDestroyed ) { + self.data.durationChange = function() { + var newDuration = self.media.duration, + newDurationPlus = newDuration + 1, + byStart = self.data.trackEvents.byStart, + byEnd = self.data.trackEvents.byEnd; + + // Remove old padding events + byStart.pop(); + byEnd.pop(); + + // Remove any internal tracking of events that have end times greater than duration + // otherwise their end events will never be hit. + for ( var k = byEnd.length - 1; k > 0; k-- ) { + if ( byEnd[ k ].end > newDuration ) { + self.removeTrackEvent( byEnd[ k ]._id ); + } + } + + // Remove any internal tracking of events that have end times greater than duration + // otherwise their end events will never be hit. + for ( var i = 0; i < byStart.length; i++ ) { + if ( byStart[ i ].end > newDuration ) { + self.removeTrackEvent( byStart[ i ]._id ); + } + } + + // References to byEnd/byStart are reset, so accessing it this way is + // forced upon us. + self.data.trackEvents.byEnd.push({ + start: newDurationPlus, + end: newDurationPlus + }); + + self.data.trackEvents.byStart.push({ + start: newDurationPlus, + end: newDurationPlus + }); + }; + + // Listen for duration changes and adjust internal tracking of event timings + self.media.addEventListener( "durationchange", self.data.durationChange, false ); + } + + if ( self.options.frameAnimation ) { + + // if Popcorn is created with frameAnimation option set to true, + // requestAnimFrame is used instead of "timeupdate" media event. + // This is for greater frame time accuracy, theoretically up to + // 60 frames per second as opposed to ~4 ( ~every 15-250ms) + self.data.timeUpdate = function () { + + Popcorn.timeUpdate( self, {} ); + + // fire frame for each enabled active plugin of every type + Popcorn.forEach( Popcorn.manifest, function( key, val ) { + + runningPlugins = self.data.running[ val ]; + + // ensure there are running plugins on this type on this instance + if ( runningPlugins ) { + + rpLength = runningPlugins.length; + for ( var i = 0; i < rpLength; i++ ) { + + runningPlugin = runningPlugins[ i ]; + rpNatives = runningPlugin._natives; + rpNatives && rpNatives.frame && + rpNatives.frame.call( self, {}, runningPlugin, self.currentTime() ); + } + } + }); + + self.emit( "timeupdate" ); + + !self.isDestroyed && requestAnimFrame( self.data.timeUpdate ); + }; + + !self.isDestroyed && requestAnimFrame( self.data.timeUpdate ); + + } else { + + self.data.timeUpdate = function( event ) { + Popcorn.timeUpdate( self, event ); + }; + + if ( !self.isDestroyed ) { + self.media.addEventListener( "timeupdate", self.data.timeUpdate, false ); + } + } + }; + + self.media.addEventListener( "error", function() { + self.error = self.media.error; + }, false ); + + // http://www.whatwg.org/specs/web-apps/current-work/#dom-media-readystate + // + // If media is in readyState (rS) >= 1, we know the media's duration, + // which is required before running the isReady function. + // If rS is 0, attach a listener for "loadedmetadata", + // ( Which indicates that the media has moved from rS 0 to 1 ) + // + // This has been changed from a check for rS 2 because + // in certain conditions, Firefox can enter this code after dropping + // to rS 1 from a higher state such as 2 or 3. This caused a "loadeddata" + // listener to be attached to the media object, an event that had + // already triggered and would not trigger again. This left Popcorn with an + // instance that could never start a timeUpdate loop. + if ( self.media.readyState >= 1 ) { + + isReady(); + } else { + + self.media.addEventListener( "loadedmetadata", isReady, false ); + } + + return this; + } + }; + + // Extend constructor prototype to instance prototype + // Allows chaining methods to instances + Popcorn.p.init.prototype = Popcorn.p; + + Popcorn.byId = function( str ) { + var instances = Popcorn.instances, + length = instances.length, + i = 0; + + for ( ; i < length; i++ ) { + if ( instances[ i ].id === str ) { + return instances[ i ]; + } + } + + return null; + }; + + Popcorn.forEach = function( obj, fn, context ) { + + if ( !obj || !fn ) { + return {}; + } + + context = context || this; + + var key, len; + + // Use native whenever possible + if ( forEach && obj.forEach === forEach ) { + return obj.forEach( fn, context ); + } + + if ( toString.call( obj ) === "[object NodeList]" ) { + for ( key = 0, len = obj.length; key < len; key++ ) { + fn.call( context, obj[ key ], key, obj ); + } + return obj; + } + + for ( key in obj ) { + if ( hasOwn.call( obj, key ) ) { + fn.call( context, obj[ key ], key, obj ); + } + } + return obj; + }; + + Popcorn.extend = function( obj ) { + var dest = obj, src = slice.call( arguments, 1 ); + + Popcorn.forEach( src, function( copy ) { + for ( var prop in copy ) { + dest[ prop ] = copy[ prop ]; + } + }); + + return dest; + }; + + + // A Few reusable utils, memoized onto Popcorn + Popcorn.extend( Popcorn, { + noConflict: function( deep ) { + + if ( deep ) { + global.Popcorn = _Popcorn; + } + + return Popcorn; + }, + error: function( msg ) { + throw new Error( msg ); + }, + guid: function( prefix ) { + Popcorn.guid.counter++; + return ( prefix ? prefix : "" ) + ( +new Date() + Popcorn.guid.counter ); + }, + sizeOf: function( obj ) { + var size = 0; + + for ( var prop in obj ) { + size++; + } + + return size; + }, + isArray: Array.isArray || function( array ) { + return toString.call( array ) === "[object Array]"; + }, + + nop: function() {}, + + position: function( elem ) { + + if ( !elem.parentNode ) { + return null; + } + + var clientRect = elem.getBoundingClientRect(), + bounds = {}, + doc = elem.ownerDocument, + docElem = document.documentElement, + body = document.body, + clientTop, clientLeft, scrollTop, scrollLeft, top, left; + + // Determine correct clientTop/Left + clientTop = docElem.clientTop || body.clientTop || 0; + clientLeft = docElem.clientLeft || body.clientLeft || 0; + + // Determine correct scrollTop/Left + scrollTop = ( global.pageYOffset && docElem.scrollTop || body.scrollTop ); + scrollLeft = ( global.pageXOffset && docElem.scrollLeft || body.scrollLeft ); + + // Temp top/left + top = Math.ceil( clientRect.top + scrollTop - clientTop ); + left = Math.ceil( clientRect.left + scrollLeft - clientLeft ); + + for ( var p in clientRect ) { + bounds[ p ] = Math.round( clientRect[ p ] ); + } + + return Popcorn.extend({}, bounds, { top: top, left: left }); + }, + + disable: function( instance, plugin ) { + + if ( instance.data.disabled[ plugin ] ) { + return; + } + + instance.data.disabled[ plugin ] = true; + + if ( plugin in Popcorn.registryByName && + instance.data.running[ plugin ] ) { + + for ( var i = instance.data.running[ plugin ].length - 1, event; i >= 0; i-- ) { + + event = instance.data.running[ plugin ][ i ]; + event._natives.end.call( instance, null, event ); + + instance.emit( "trackend", + Popcorn.extend({}, event, { + plugin: event.type, + type: "trackend" + }) + ); + } + } + + return instance; + }, + enable: function( instance, plugin ) { + + if ( !instance.data.disabled[ plugin ] ) { + return; + } + + instance.data.disabled[ plugin ] = false; + + if ( plugin in Popcorn.registryByName && + instance.data.running[ plugin ] ) { + + for ( var i = instance.data.running[ plugin ].length - 1, event; i >= 0; i-- ) { + + event = instance.data.running[ plugin ][ i ]; + event._natives.start.call( instance, null, event ); + + instance.emit( "trackstart", + Popcorn.extend({}, event, { + plugin: event.type, + type: "trackstart", + track: event + }) + ); + } + } + + return instance; + }, + destroy: function( instance ) { + var events = instance.data.events, + trackEvents = instance.data.trackEvents, + singleEvent, item, fn, plugin; + + // Iterate through all events and remove them + for ( item in events ) { + singleEvent = events[ item ]; + for ( fn in singleEvent ) { + delete singleEvent[ fn ]; + } + events[ item ] = null; + } + + // remove all plugins off the given instance + for ( plugin in Popcorn.registryByName ) { + Popcorn.removePlugin( instance, plugin ); + } + + // Remove all data.trackEvents #1178 + trackEvents.byStart.length = 0; + trackEvents.byEnd.length = 0; + + if ( !instance.isDestroyed ) { + instance.data.timeUpdate && instance.media.removeEventListener( "timeupdate", instance.data.timeUpdate, false ); + instance.isDestroyed = true; + } + + Popcorn.instances.splice( Popcorn.instances.indexOf( instance ), 1 ); + } + }); + + // Memoized GUID Counter + Popcorn.guid.counter = 1; + + // Factory to implement getters, setters and controllers + // as Popcorn instance methods. The IIFE will create and return + // an object with defined methods + Popcorn.extend(Popcorn.p, (function() { + + var methods = "load play pause currentTime playbackRate volume duration preload playbackRate " + + "autoplay loop controls muted buffered readyState seeking paused played seekable ended", + ret = {}; + + + // Build methods, store in object that is returned and passed to extend + Popcorn.forEach( methods.split( /\s+/g ), function( name ) { + + ret[ name ] = function( arg ) { + var previous; + + if ( typeof this.media[ name ] === "function" ) { + + // Support for shorthanded play(n)/pause(n) jump to currentTime + // If arg is not null or undefined and called by one of the + // allowed shorthandable methods, then set the currentTime + // Supports time as seconds or SMPTE + if ( arg != null && /play|pause/.test( name ) ) { + this.media.currentTime = Popcorn.util.toSeconds( arg ); + } + + this.media[ name ](); + + return this; + } + + if ( arg != null ) { + // Capture the current value of the attribute property + previous = this.media[ name ]; + + // Set the attribute property with the new value + this.media[ name ] = arg; + + // If the new value is not the same as the old value + // emit an "attrchanged event" + if ( previous !== arg ) { + this.emit( "attrchange", { + attribute: name, + previousValue: previous, + currentValue: arg + }); + } + return this; + } + + return this.media[ name ]; + }; + }); + + return ret; + + })() + ); + + Popcorn.forEach( "enable disable".split(" "), function( method ) { + Popcorn.p[ method ] = function( plugin ) { + return Popcorn[ method ]( this, plugin ); + }; + }); + + Popcorn.extend(Popcorn.p, { + + // Rounded currentTime + roundTime: function() { + return Math.round( this.media.currentTime ); + }, + + // Attach an event to a single point in time + exec: function( id, time, fn ) { + var length = arguments.length, + eventType = "trackadded", + trackEvent, sec, options; + + // Check if first could possibly be a SMPTE string + // p.cue( "smpte string", fn ); + // try/catch avoid awful throw in Popcorn.util.toSeconds + // TODO: Get rid of that, replace with NaN return? + try { + sec = Popcorn.util.toSeconds( id ); + } catch ( e ) {} + + // If it can be converted into a number then + // it's safe to assume that the string was SMPTE + if ( typeof sec === "number" ) { + id = sec; + } + + // Shift arguments based on use case + // + // Back compat for: + // p.cue( time, fn ); + if ( typeof id === "number" && length === 2 ) { + fn = time; + time = id; + id = Popcorn.guid( "cue" ); + } else { + // Support for new forms + + // p.cue( "empty-cue" ); + if ( length === 1 ) { + // Set a time for an empty cue. It's not important what + // the time actually is, because the cue is a no-op + time = -1; + + } else { + + // Get the TrackEvent that matches the given id. + trackEvent = this.getTrackEvent( id ); + + if ( trackEvent ) { + + // remove existing cue so a new one can be added via trackEvents.add + this.data.trackEvents.remove( id ); + TrackEvent.end( this, trackEvent ); + // Update track event references + Popcorn.removeTrackEvent.ref( this, id ); + + eventType = "cuechange"; + + // p.cue( "my-id", 12 ); + // p.cue( "my-id", function() { ... }); + if ( typeof id === "string" && length === 2 ) { + + // p.cue( "my-id", 12 ); + // The path will update the cue time. + if ( typeof time === "number" ) { + // Re-use existing TrackEvent start callback + fn = trackEvent._natives.start; + } + + // p.cue( "my-id", function() { ... }); + // The path will update the cue function + if ( typeof time === "function" ) { + fn = time; + // Re-use existing TrackEvent start time + time = trackEvent.start; + } + } + } else { + + if ( length >= 2 ) { + + // p.cue( "a", "00:00:00"); + if ( typeof time === "string" ) { + try { + sec = Popcorn.util.toSeconds( time ); + } catch ( e ) {} + + time = sec; + } + + // p.cue( "b", 11 ); + // p.cue( "b", 11, function() {} ); + if ( typeof time === "number" ) { + fn = fn || Popcorn.nop(); + } + + // p.cue( "c", function() {}); + if ( typeof time === "function" ) { + fn = time; + time = -1; + } + } + } + } + } + + options = { + id: id, + start: time, + end: time + 1, + _running: false, + _natives: { + start: fn || Popcorn.nop, + end: Popcorn.nop, + type: "cue" + } + }; + + if ( trackEvent ) { + options = Popcorn.extend( trackEvent, options ); + } + + if ( eventType === "cuechange" ) { + + // Supports user defined track event id + options._id = options.id || options._id || Popcorn.guid( options._natives.type ); + + this.data.trackEvents.add( options ); + TrackEvent.start( this, options ); + + this.timeUpdate( this, null, true ); + + // Store references to user added trackevents in ref table + Popcorn.addTrackEvent.ref( this, options ); + + this.emit( eventType, Popcorn.extend({}, options, { + id: id, + type: eventType, + previousValue: { + time: trackEvent.start, + fn: trackEvent._natives.start + }, + currentValue: { + time: time, + fn: fn || Popcorn.nop + }, + track: trackEvent + })); + } else { + // Creating a one second track event with an empty end + Popcorn.addTrackEvent( this, options ); + } + + return this; + }, + + // Mute the calling media, optionally toggle + mute: function( toggle ) { + + var event = toggle == null || toggle === true ? "muted" : "unmuted"; + + // If `toggle` is explicitly `false`, + // unmute the media and restore the volume level + if ( event === "unmuted" ) { + this.media.muted = false; + this.media.volume = this.data.state.volume; + } + + // If `toggle` is either null or undefined, + // save the current volume and mute the media element + if ( event === "muted" ) { + this.data.state.volume = this.media.volume; + this.media.muted = true; + } + + // Trigger either muted|unmuted event + this.emit( event ); + + return this; + }, + + // Convenience method, unmute the calling media + unmute: function( toggle ) { + + return this.mute( toggle == null ? false : !toggle ); + }, + + // Get the client bounding box of an instance element + position: function() { + return Popcorn.position( this.media ); + }, + + // Toggle a plugin's playback behaviour (on or off) per instance + toggle: function( plugin ) { + return Popcorn[ this.data.disabled[ plugin ] ? "enable" : "disable" ]( this, plugin ); + }, + + // Set default values for plugin options objects per instance + defaults: function( plugin, defaults ) { + + // If an array of default configurations is provided, + // iterate and apply each to this instance + if ( Popcorn.isArray( plugin ) ) { + + Popcorn.forEach( plugin, function( obj ) { + for ( var name in obj ) { + this.defaults( name, obj[ name ] ); + } + }, this ); + + return this; + } + + if ( !this.options.defaults ) { + this.options.defaults = {}; + } + + if ( !this.options.defaults[ plugin ] ) { + this.options.defaults[ plugin ] = {}; + } + + Popcorn.extend( this.options.defaults[ plugin ], defaults ); + + return this; + } + }); + + Popcorn.Events = { + UIEvents: "blur focus focusin focusout load resize scroll unload", + MouseEvents: "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave click dblclick", + Events: "loadstart progress suspend emptied stalled play pause error " + + "loadedmetadata loadeddata waiting playing canplay canplaythrough " + + "seeking seeked timeupdate ended ratechange durationchange volumechange" + }; + + Popcorn.Events.Natives = Popcorn.Events.UIEvents + " " + + Popcorn.Events.MouseEvents + " " + + Popcorn.Events.Events; + + internal.events.apiTypes = [ "UIEvents", "MouseEvents", "Events" ]; + + // Privately compile events table at load time + (function( events, data ) { + + var apis = internal.events.apiTypes, + eventsList = events.Natives.split( /\s+/g ), + idx = 0, len = eventsList.length, prop; + + for( ; idx < len; idx++ ) { + data.hash[ eventsList[idx] ] = true; + } + + apis.forEach(function( val, idx ) { + + data.apis[ val ] = {}; + + var apiEvents = events[ val ].split( /\s+/g ), + len = apiEvents.length, + k = 0; + + for ( ; k < len; k++ ) { + data.apis[ val ][ apiEvents[ k ] ] = true; + } + }); + })( Popcorn.Events, internal.events ); + + Popcorn.events = { + + isNative: function( type ) { + return !!internal.events.hash[ type ]; + }, + getInterface: function( type ) { + + if ( !Popcorn.events.isNative( type ) ) { + return false; + } + + var eventApi = internal.events, + apis = eventApi.apiTypes, + apihash = eventApi.apis, + idx = 0, len = apis.length, api, tmp; + + for ( ; idx < len; idx++ ) { + tmp = apis[ idx ]; + + if ( apihash[ tmp ][ type ] ) { + api = tmp; + break; + } + } + return api; + }, + // Compile all native events to single array + all: Popcorn.Events.Natives.split( /\s+/g ), + // Defines all Event handling static functions + fn: { + trigger: function( type, data ) { + var eventInterface, evt, clonedEvents, + events = this.data.events[ type ]; + + // setup checks for custom event system + if ( events ) { + eventInterface = Popcorn.events.getInterface( type ); + + if ( eventInterface ) { + evt = document.createEvent( eventInterface ); + evt.initEvent( type, true, true, global, 1 ); + + this.media.dispatchEvent( evt ); + + return this; + } + + // clone events in case callbacks remove callbacks themselves + clonedEvents = events.slice(); + + // iterate through all callbacks + while ( clonedEvents.length ) { + clonedEvents.shift().call( this, data ); + } + } + + return this; + }, + listen: function( type, fn ) { + var self = this, + hasEvents = true, + eventHook = Popcorn.events.hooks[ type ], + origType = type, + clonedEvents, + tmp; + + if ( typeof fn !== "function" ) { + throw new Error( "Popcorn.js Error: Listener is not a function" ); + } + + // Setup event registry entry + if ( !this.data.events[ type ] ) { + this.data.events[ type ] = []; + // Toggle if the previous assumption was untrue + hasEvents = false; + } + + // Check and setup event hooks + if ( eventHook ) { + // Execute hook add method if defined + if ( eventHook.add ) { + eventHook.add.call( this, {}, fn ); + } + + // Reassign event type to our piggyback event type if defined + if ( eventHook.bind ) { + type = eventHook.bind; + } + + // Reassign handler if defined + if ( eventHook.handler ) { + tmp = fn; + + fn = function wrapper( event ) { + eventHook.handler.call( self, event, tmp ); + }; + } + + // assume the piggy back event is registered + hasEvents = true; + + // Setup event registry entry + if ( !this.data.events[ type ] ) { + this.data.events[ type ] = []; + // Toggle if the previous assumption was untrue + hasEvents = false; + } + } + + // Register event and handler + this.data.events[ type ].push( fn ); + + // only attach one event of any type + if ( !hasEvents && Popcorn.events.all.indexOf( type ) > -1 ) { + this.media.addEventListener( type, function( event ) { + if ( self.data.events[ type ] ) { + // clone events in case callbacks remove callbacks themselves + clonedEvents = self.data.events[ type ].slice(); + + // iterate through all callbacks + while ( clonedEvents.length ) { + clonedEvents.shift().call( self, event ); + } + } + }, false ); + } + return this; + }, + unlisten: function( type, fn ) { + var ind, + events = this.data.events[ type ]; + + if ( !events ) { + return; // no listeners = nothing to do + } + + if ( typeof fn === "string" ) { + // legacy support for string-based removal -- not recommended + for ( var i = 0; i < events.length; i++ ) { + if ( events[ i ].name === fn ) { + // decrement i because array length just got smaller + events.splice( i--, 1 ); + } + } + + return this; + } else if ( typeof fn === "function" ) { + while( ind !== -1 ) { + ind = events.indexOf( fn ); + if ( ind !== -1 ) { + events.splice( ind, 1 ); + } + } + + return this; + } + + // if we got to this point, we are deleting all functions of this type + this.data.events[ type ] = null; + + return this; + } + }, + hooks: { + canplayall: { + bind: "canplaythrough", + add: function( event, callback ) { + + var state = false; + + if ( this.media.readyState ) { + + // always call canplayall asynchronously + setTimeout(function() { + callback.call( this, event ); + }.bind(this), 0 ); + + state = true; + } + + this.data.hooks.canplayall = { + fired: state + }; + }, + // declare special handling instructions + handler: function canplayall( event, callback ) { + + if ( !this.data.hooks.canplayall.fired ) { + // trigger original user callback once + callback.call( this, event ); + + this.data.hooks.canplayall.fired = true; + } + } + } + } + }; + + // Extend Popcorn.events.fns (listen, unlisten, trigger) to all Popcorn instances + // Extend aliases (on, off, emit) + Popcorn.forEach( [ [ "trigger", "emit" ], [ "listen", "on" ], [ "unlisten", "off" ] ], function( key ) { + Popcorn.p[ key[ 0 ] ] = Popcorn.p[ key[ 1 ] ] = Popcorn.events.fn[ key[ 0 ] ]; + }); + + // Internal Only - construct simple "TrackEvent" + // data type objects + function TrackEvent( track ) { + Abstract.put.call( this, track ); + } + + // Determine if a TrackEvent's "start" and "trackstart" must be called. + TrackEvent.start = function( instance, track ) { + + if ( track.end > instance.media.currentTime && + track.start <= instance.media.currentTime && !track._running ) { + + track._running = true; + instance.data.running[ track._natives.type ].push( track ); + + if ( !instance.data.disabled[ track._natives.type ] ) { + + track._natives.start.call( instance, null, track ); + + instance.emit( "trackstart", + Popcorn.extend( {}, track, { + plugin: track._natives.type, + type: "trackstart", + track: track + }) + ); + } + } + }; + + // Determine if a TrackEvent's "end" and "trackend" must be called. + TrackEvent.end = function( instance, track ) { + + var runningPlugins; + + if ( ( track.end <= instance.media.currentTime || + track.start > instance.media.currentTime ) && track._running ) { + + runningPlugins = instance.data.running[ track._natives.type ]; + + track._running = false; + runningPlugins.splice( runningPlugins.indexOf( track ), 1 ); + + if ( !instance.data.disabled[ track._natives.type ] ) { + + track._natives.end.call( instance, null, track ); + + instance.emit( "trackend", + Popcorn.extend( {}, track, { + plugin: track._natives.type, + type: "trackend", + track: track + }) + ); + } + } + }; + + // Internal Only - construct "TrackEvents" + // data type objects that are used by the Popcorn + // instance, stored at p.data.trackEvents + function TrackEvents( parent ) { + this.parent = parent; + + this.byStart = [{ + start: -1, + end: -1 + }]; + + this.byEnd = [{ + start: -1, + end: -1 + }]; + this.animating = []; + this.startIndex = 0; + this.endIndex = 0; + this.previousUpdateTime = -1; + + this.count = 1; + } + + function isMatch( obj, key, value ) { + return obj[ key ] && obj[ key ] === value; + } + + TrackEvents.prototype.where = function( params ) { + return ( this.parent.getTrackEvents() || [] ).filter(function( event ) { + var key, value; + + // If no explicit params, match all TrackEvents + if ( !params ) { + return true; + } + + // Filter keys in params against both the top level properties + // and the _natives properties + for ( key in params ) { + value = params[ key ]; + if ( isMatch( event, key, value ) || isMatch( event._natives, key, value ) ) { + return true; + } + } + return false; + }); + }; + + TrackEvents.prototype.add = function( track ) { + + // Store this definition in an array sorted by times + var byStart = this.byStart, + byEnd = this.byEnd, + startIndex, endIndex; + + // Push track event ids into the history + if ( track && track._id ) { + this.parent.data.history.push( track._id ); + } + + track.start = Popcorn.util.toSeconds( track.start, this.parent.options.framerate ); + track.end = Popcorn.util.toSeconds( track.end, this.parent.options.framerate ); + + for ( startIndex = byStart.length - 1; startIndex >= 0; startIndex-- ) { + + if ( track.start >= byStart[ startIndex ].start ) { + byStart.splice( startIndex + 1, 0, track ); + break; + } + } + + for ( endIndex = byEnd.length - 1; endIndex >= 0; endIndex-- ) { + + if ( track.end > byEnd[ endIndex ].end ) { + byEnd.splice( endIndex + 1, 0, track ); + break; + } + } + + // update startIndex and endIndex + if ( startIndex <= this.parent.data.trackEvents.startIndex && + track.start <= this.parent.data.trackEvents.previousUpdateTime ) { + + this.parent.data.trackEvents.startIndex++; + } + + if ( endIndex <= this.parent.data.trackEvents.endIndex && + track.end < this.parent.data.trackEvents.previousUpdateTime ) { + + this.parent.data.trackEvents.endIndex++; + } + + this.count++; + + }; + + TrackEvents.prototype.remove = function( removeId, state ) { + + if ( removeId instanceof TrackEvent ) { + removeId = removeId.id; + } + + if ( typeof removeId === "object" ) { + // Filter by key=val and remove all matching TrackEvents + this.where( removeId ).forEach(function( event ) { + // |this| refers to the calling Popcorn "parent" instance + this.removeTrackEvent( event._id ); + }, this.parent ); + + return this; + } + + var start, end, animate, historyLen, track, + length = this.byStart.length, + index = 0, + indexWasAt = 0, + byStart = [], + byEnd = [], + animating = [], + history = [], + comparable = {}; + + state = state || {}; + + while ( --length > -1 ) { + start = this.byStart[ index ]; + end = this.byEnd[ index ]; + + // Padding events will not have _id properties. + // These should be safely pushed onto the front and back of the + // track event array + if ( !start._id ) { + byStart.push( start ); + byEnd.push( end ); + } + + // Filter for user track events (vs system track events) + if ( start._id ) { + + // If not a matching start event for removal + if ( start._id !== removeId ) { + byStart.push( start ); + } + + // If not a matching end event for removal + if ( end._id !== removeId ) { + byEnd.push( end ); + } + + // If the _id is matched, capture the current index + if ( start._id === removeId ) { + indexWasAt = index; + + // cache the track event being removed + track = start; + } + } + // Increment the track index + index++; + } + + // Reset length to be used by the condition below to determine + // if animating track events should also be filtered for removal. + // Reset index below to be used by the reverse while as an + // incrementing counter + length = this.animating.length; + index = 0; + + if ( length ) { + while ( --length > -1 ) { + animate = this.animating[ index ]; + + // Padding events will not have _id properties. + // These should be safely pushed onto the front and back of the + // track event array + if ( !animate._id ) { + animating.push( animate ); + } + + // If not a matching animate event for removal + if ( animate._id && animate._id !== removeId ) { + animating.push( animate ); + } + // Increment the track index + index++; + } + } + + // Update + if ( indexWasAt <= this.startIndex ) { + this.startIndex--; + } + + if ( indexWasAt <= this.endIndex ) { + this.endIndex--; + } + + this.byStart = byStart; + this.byEnd = byEnd; + this.animating = animating; + this.count--; + + historyLen = this.parent.data.history.length; + + for ( var i = 0; i < historyLen; i++ ) { + if ( this.parent.data.history[ i ] !== removeId ) { + history.push( this.parent.data.history[ i ] ); + } + } + + // Update ordered history array + this.parent.data.history = history; + + }; + + // Helper function used to retrieve old values of properties that + // are provided for update. + function getPreviousProperties( oldOptions, newOptions ) { + var matchProps = {}; + + for ( var prop in oldOptions ) { + if ( hasOwn.call( newOptions, prop ) && hasOwn.call( oldOptions, prop ) ) { + matchProps[ prop ] = oldOptions[ prop ]; + } + } + + return matchProps; + } + + // Internal Only - Adds track events to the instance object + Popcorn.addTrackEvent = function( obj, track ) { + var temp; + + if ( track instanceof TrackEvent ) { + return; + } + + track = new TrackEvent( track ); + + // Determine if this track has default options set for it + // If so, apply them to the track object + if ( track && track._natives && track._natives.type && + ( obj.options.defaults && obj.options.defaults[ track._natives.type ] ) ) { + + // To ensure that the TrackEvent Invariant Policy is enforced, + // First, copy the properties of the newly created track event event + // to a temporary holder + temp = Popcorn.extend( {}, track ); + + // Next, copy the default onto the newly created trackevent, followed by the + // temporary holder. + Popcorn.extend( track, obj.options.defaults[ track._natives.type ], temp ); + } + + if ( track._natives ) { + // Supports user defined track event id + track._id = track.id || track._id || Popcorn.guid( track._natives.type ); + + // Trigger _setup method if exists + if ( track._natives._setup ) { + + track._natives._setup.call( obj, track ); + + obj.emit( "tracksetup", Popcorn.extend( {}, track, { + plugin: track._natives.type, + type: "tracksetup", + track: track + })); + } + } + + obj.data.trackEvents.add( track ); + TrackEvent.start( obj, track ); + + this.timeUpdate( obj, null, true ); + + // Store references to user added trackevents in ref table + if ( track._id ) { + Popcorn.addTrackEvent.ref( obj, track ); + } + + obj.emit( "trackadded", Popcorn.extend({}, track, + track._natives ? { plugin: track._natives.type } : {}, { + type: "trackadded", + track: track + })); + }; + + // Internal Only - Adds track event references to the instance object's trackRefs hash table + Popcorn.addTrackEvent.ref = function( obj, track ) { + obj.data.trackRefs[ track._id ] = track; + + return obj; + }; + + Popcorn.removeTrackEvent = function( obj, removeId ) { + var track = obj.getTrackEvent( removeId ); + + if ( !track ) { + return; + } + + // If a _teardown function was defined, + // enforce for track event removals + if ( track._natives._teardown ) { + track._natives._teardown.call( obj, track ); + } + + obj.data.trackEvents.remove( removeId ); + + // Update track event references + Popcorn.removeTrackEvent.ref( obj, removeId ); + + if ( track._natives ) { + + // Fire a trackremoved event + obj.emit( "trackremoved", Popcorn.extend({}, track, { + plugin: track._natives.type, + type: "trackremoved", + track: track + })); + } + }; + + // Internal Only - Removes track event references from instance object's trackRefs hash table + Popcorn.removeTrackEvent.ref = function( obj, removeId ) { + delete obj.data.trackRefs[ removeId ]; + + return obj; + }; + + // Return an array of track events bound to this instance object + Popcorn.getTrackEvents = function( obj ) { + + var trackevents = [], + refs = obj.data.trackEvents.byStart, + length = refs.length, + idx = 0, + ref; + + for ( ; idx < length; idx++ ) { + ref = refs[ idx ]; + // Return only user attributed track event references + if ( ref._id ) { + trackevents.push( ref ); + } + } + + return trackevents; + }; + + // Internal Only - Returns an instance object's trackRefs hash table + Popcorn.getTrackEvents.ref = function( obj ) { + return obj.data.trackRefs; + }; + + // Return a single track event bound to this instance object + Popcorn.getTrackEvent = function( obj, trackId ) { + return obj.data.trackRefs[ trackId ]; + }; + + // Internal Only - Returns an instance object's track reference by track id + Popcorn.getTrackEvent.ref = function( obj, trackId ) { + return obj.data.trackRefs[ trackId ]; + }; + + Popcorn.getLastTrackEventId = function( obj ) { + return obj.data.history[ obj.data.history.length - 1 ]; + }; + + Popcorn.timeUpdate = function( obj, event ) { + var currentTime = obj.media.currentTime, + previousTime = obj.data.trackEvents.previousUpdateTime, + tracks = obj.data.trackEvents, + end = tracks.endIndex, + start = tracks.startIndex, + byStartLen = tracks.byStart.length, + byEndLen = tracks.byEnd.length, + registryByName = Popcorn.registryByName, + trackstart = "trackstart", + trackend = "trackend", + + byEnd, byStart, byAnimate, natives, type, runningPlugins; + + // Playbar advancing + if ( previousTime <= currentTime ) { + + while ( tracks.byEnd[ end ] && tracks.byEnd[ end ].end <= currentTime ) { + + byEnd = tracks.byEnd[ end ]; + natives = byEnd._natives; + type = natives && natives.type; + + // If plugin does not exist on this instance, remove it + if ( !natives || + ( !!registryByName[ type ] || + !!obj[ type ] ) ) { + + if ( byEnd._running === true ) { + + byEnd._running = false; + runningPlugins = obj.data.running[ type ]; + runningPlugins.splice( runningPlugins.indexOf( byEnd ), 1 ); + + if ( !obj.data.disabled[ type ] ) { + + natives.end.call( obj, event, byEnd ); + + obj.emit( trackend, + Popcorn.extend({}, byEnd, { + plugin: type, + type: trackend, + track: byEnd + }) + ); + } + } + + end++; + } else { + // remove track event + Popcorn.removeTrackEvent( obj, byEnd._id ); + return; + } + } + + while ( tracks.byStart[ start ] && tracks.byStart[ start ].start <= currentTime ) { + + byStart = tracks.byStart[ start ]; + natives = byStart._natives; + type = natives && natives.type; + // If plugin does not exist on this instance, remove it + if ( !natives || + ( !!registryByName[ type ] || + !!obj[ type ] ) ) { + if ( byStart.end > currentTime && + byStart._running === false ) { + + byStart._running = true; + obj.data.running[ type ].push( byStart ); + + if ( !obj.data.disabled[ type ] ) { + + natives.start.call( obj, event, byStart ); + + obj.emit( trackstart, + Popcorn.extend({}, byStart, { + plugin: type, + type: trackstart, + track: byStart + }) + ); + } + } + start++; + } else { + // remove track event + Popcorn.removeTrackEvent( obj, byStart._id ); + return; + } + } + + // Playbar receding + } else if ( previousTime > currentTime ) { + + while ( tracks.byStart[ start ] && tracks.byStart[ start ].start > currentTime ) { + + byStart = tracks.byStart[ start ]; + natives = byStart._natives; + type = natives && natives.type; + + // if plugin does not exist on this instance, remove it + if ( !natives || + ( !!registryByName[ type ] || + !!obj[ type ] ) ) { + + if ( byStart._running === true ) { + + byStart._running = false; + runningPlugins = obj.data.running[ type ]; + runningPlugins.splice( runningPlugins.indexOf( byStart ), 1 ); + + if ( !obj.data.disabled[ type ] ) { + + natives.end.call( obj, event, byStart ); + + obj.emit( trackend, + Popcorn.extend({}, byStart, { + plugin: type, + type: trackend, + track: byStart + }) + ); + } + } + start--; + } else { + // remove track event + Popcorn.removeTrackEvent( obj, byStart._id ); + return; + } + } + + while ( tracks.byEnd[ end ] && tracks.byEnd[ end ].end > currentTime ) { + + byEnd = tracks.byEnd[ end ]; + natives = byEnd._natives; + type = natives && natives.type; + + // if plugin does not exist on this instance, remove it + if ( !natives || + ( !!registryByName[ type ] || + !!obj[ type ] ) ) { + + if ( byEnd.start <= currentTime && + byEnd._running === false ) { + + byEnd._running = true; + obj.data.running[ type ].push( byEnd ); + + if ( !obj.data.disabled[ type ] ) { + + natives.start.call( obj, event, byEnd ); + + obj.emit( trackstart, + Popcorn.extend({}, byEnd, { + plugin: type, + type: trackstart, + track: byEnd + }) + ); + } + } + end--; + } else { + // remove track event + Popcorn.removeTrackEvent( obj, byEnd._id ); + return; + } + } + } + + tracks.endIndex = end; + tracks.startIndex = start; + tracks.previousUpdateTime = currentTime; + + //enforce index integrity if trackRemoved + tracks.byStart.length < byStartLen && tracks.startIndex--; + tracks.byEnd.length < byEndLen && tracks.endIndex--; + + }; + + // Map and Extend TrackEvent functions to all Popcorn instances + Popcorn.extend( Popcorn.p, { + + getTrackEvents: function() { + return Popcorn.getTrackEvents.call( null, this ); + }, + + getTrackEvent: function( id ) { + return Popcorn.getTrackEvent.call( null, this, id ); + }, + + getLastTrackEventId: function() { + return Popcorn.getLastTrackEventId.call( null, this ); + }, + + removeTrackEvent: function( id ) { + + Popcorn.removeTrackEvent.call( null, this, id ); + return this; + }, + + removePlugin: function( name ) { + Popcorn.removePlugin.call( null, this, name ); + return this; + }, + + timeUpdate: function( event ) { + Popcorn.timeUpdate.call( null, this, event ); + return this; + }, + + destroy: function() { + Popcorn.destroy.call( null, this ); + return this; + } + }); + + // Plugin manifests + Popcorn.manifest = {}; + // Plugins are registered + Popcorn.registry = []; + Popcorn.registryByName = {}; + // An interface for extending Popcorn + // with plugin functionality + Popcorn.plugin = function( name, definition, manifest ) { + + if ( Popcorn.protect.natives.indexOf( name.toLowerCase() ) >= 0 ) { + Popcorn.error( "'" + name + "' is a protected function name" ); + return; + } + + // Provides some sugar, but ultimately extends + // the definition into Popcorn.p + var isfn = typeof definition === "function", + blacklist = [ "start", "end", "type", "manifest" ], + methods = [ "_setup", "_teardown", "start", "end", "frame" ], + plugin = {}, + setup; + + // combines calls of two function calls into one + var combineFn = function( first, second ) { + + first = first || Popcorn.nop; + second = second || Popcorn.nop; + + return function() { + first.apply( this, arguments ); + second.apply( this, arguments ); + }; + }; + + // If `manifest` arg is undefined, check for manifest within the `definition` object + // If no `definition.manifest`, an empty object is a sufficient fallback + Popcorn.manifest[ name ] = manifest = manifest || definition.manifest || {}; + + // apply safe, and empty default functions + methods.forEach(function( method ) { + definition[ method ] = safeTry( definition[ method ] || Popcorn.nop, name ); + }); + + var pluginFn = function( setup, options ) { + + if ( !options ) { + return this; + } + + // When the "ranges" property is set and its value is an array, short-circuit + // the pluginFn definition to recall itself with an options object generated from + // each range object in the ranges array. (eg. { start: 15, end: 16 } ) + if ( options.ranges && Popcorn.isArray(options.ranges) ) { + Popcorn.forEach( options.ranges, function( range ) { + // Create a fresh object, extend with current options + // and start/end range object's properties + // Works with in/out as well. + var opts = Popcorn.extend( {}, options, range ); + + // Remove the ranges property to prevent infinitely + // entering this condition + delete opts.ranges; + + // Call the plugin with the newly created opts object + this[ name ]( opts ); + }, this); + + // Return the Popcorn instance to avoid creating an empty track event + return this; + } + + // Storing the plugin natives + var natives = options._natives = {}, + compose = "", + originalOpts, manifestOpts; + + Popcorn.extend( natives, setup ); + + options._natives.type = options._natives.plugin = name; + options._running = false; + + natives.start = natives.start || natives[ "in" ]; + natives.end = natives.end || natives[ "out" ]; + + if ( options.once ) { + natives.end = combineFn( natives.end, function() { + this.removeTrackEvent( options._id ); + }); + } + + // extend teardown to always call end if running + natives._teardown = combineFn(function() { + + var args = slice.call( arguments ), + runningPlugins = this.data.running[ natives.type ]; + + // end function signature is not the same as teardown, + // put null on the front of arguments for the event parameter + args.unshift( null ); + + // only call end if event is running + args[ 1 ]._running && + runningPlugins.splice( runningPlugins.indexOf( options ), 1 ) && + natives.end.apply( this, args ); + + args[ 1 ]._running = false; + this.emit( "trackend", + Popcorn.extend( {}, options, { + plugin: natives.type, + type: "trackend", + track: Popcorn.getTrackEvent( this, options.id || options._id ) + }) + ); + }, natives._teardown ); + + // extend teardown to always trigger trackteardown after teardown + natives._teardown = combineFn( natives._teardown, function() { + + this.emit( "trackteardown", Popcorn.extend( {}, options, { + plugin: name, + type: "trackteardown", + track: Popcorn.getTrackEvent( this, options.id || options._id ) + })); + }); + + // default to an empty string if no effect exists + // split string into an array of effects + options.compose = options.compose || []; + if ( typeof options.compose === "string" ) { + options.compose = options.compose.split( " " ); + } + options.effect = options.effect || []; + if ( typeof options.effect === "string" ) { + options.effect = options.effect.split( " " ); + } + + // join the two arrays together + options.compose = options.compose.concat( options.effect ); + + options.compose.forEach(function( composeOption ) { + + // if the requested compose is garbage, throw it away + compose = Popcorn.compositions[ composeOption ] || {}; + + // extends previous functions with compose function + methods.forEach(function( method ) { + natives[ method ] = combineFn( natives[ method ], compose[ method ] ); + }); + }); + + // Ensure a manifest object, an empty object is a sufficient fallback + options._natives.manifest = manifest; + + // Checks for expected properties + if ( !( "start" in options ) ) { + options.start = options[ "in" ] || 0; + } + + if ( !options.end && options.end !== 0 ) { + options.end = options[ "out" ] || Number.MAX_VALUE; + } + + // Use hasOwn to detect non-inherited toString, since all + // objects will receive a toString - its otherwise undetectable + if ( !hasOwn.call( options, "toString" ) ) { + options.toString = function() { + var props = [ + "start: " + options.start, + "end: " + options.end, + "id: " + (options.id || options._id) + ]; + + // Matches null and undefined, allows: false, 0, "" and truthy + if ( options.target != null ) { + props.push( "target: " + options.target ); + } + + return name + " ( " + props.join(", ") + " )"; + }; + } + + // Resolves 239, 241, 242 + if ( !options.target ) { + + // Sometimes the manifest may be missing entirely + // or it has an options object that doesn't have a `target` property + manifestOpts = "options" in manifest && manifest.options; + + options.target = manifestOpts && "target" in manifestOpts && manifestOpts.target; + } + + if ( !options._id && options._natives ) { + // ensure an initial id is there before setup is called + options._id = Popcorn.guid( options._natives.type ); + } + + if ( options instanceof TrackEvent ) { + + if ( options._natives ) { + // Supports user defined track event id + options._id = options.id || options._id || Popcorn.guid( options._natives.type ); + + // Trigger _setup method if exists + if ( options._natives._setup ) { + + options._natives._setup.call( this, options ); + + this.emit( "tracksetup", Popcorn.extend( {}, options, { + plugin: options._natives.type, + type: "tracksetup", + track: options + })); + } + } + + this.data.trackEvents.add( options ); + TrackEvent.start( this, options ); + + this.timeUpdate( this, null, true ); + + // Store references to user added trackevents in ref table + if ( options._id ) { + Popcorn.addTrackEvent.ref( this, options ); + } + } else { + // Create new track event for this instance + Popcorn.addTrackEvent( this, options ); + } + + // Future support for plugin event definitions + // for all of the native events + Popcorn.forEach( setup, function( callback, type ) { + // Don't attempt to create events for certain properties: + // "start", "end", "type", "manifest". Fixes #1365 + if ( blacklist.indexOf( type ) === -1 ) { + this.on( type, callback ); + } + }, this ); + + return this; + }; + + // Extend Popcorn.p with new named definition + // Assign new named definition + Popcorn.p[ name ] = plugin[ name ] = function( id, options ) { + var length = arguments.length, + trackEvent, defaults, mergedSetupOpts, previousOpts, newOpts; + + // Shift arguments based on use case + // + // Back compat for: + // p.plugin( options ); + if ( id && !options ) { + options = id; + id = null; + } else { + + // Get the trackEvent that matches the given id. + trackEvent = this.getTrackEvent( id ); + + // If the track event does not exist, ensure that the options + // object has a proper id + if ( !trackEvent ) { + options.id = id; + + // If the track event does exist, merge the updated properties + } else { + + newOpts = options; + previousOpts = getPreviousProperties( trackEvent, newOpts ); + + // Call the plugins defined update method if provided. Allows for + // custom defined updating for a track event to be defined by the plugin author + if ( trackEvent._natives._update ) { + + this.data.trackEvents.remove( trackEvent ); + + // It's safe to say that the intent of Start/End will never change + // Update them first before calling update + if ( hasOwn.call( options, "start" ) ) { + trackEvent.start = options.start; + } + + if ( hasOwn.call( options, "end" ) ) { + trackEvent.end = options.end; + } + + TrackEvent.end( this, trackEvent ); + + if ( isfn ) { + definition.call( this, trackEvent ); + } + + trackEvent._natives._update.call( this, trackEvent, options ); + + this.data.trackEvents.add( trackEvent ); + TrackEvent.start( this, trackEvent ); + } else { + // This branch is taken when there is no explicitly defined + // _update method for a plugin. Which will occur either explicitly or + // as a result of the plugin definition being a function that _returns_ + // a definition object. + // + // In either case, this path can ONLY be reached for TrackEvents that + // already exist. + + // Directly update the TrackEvent instance. + // This supports TrackEvent invariant enforcement. + Popcorn.extend( trackEvent, options ); + + this.data.trackEvents.remove( id ); + + // If a _teardown function was defined, + // enforce for track event removals + if ( trackEvent._natives._teardown ) { + trackEvent._natives._teardown.call( this, trackEvent ); + } + + // Update track event references + Popcorn.removeTrackEvent.ref( this, id ); + + if ( isfn ) { + pluginFn.call( this, definition.call( this, trackEvent ), trackEvent ); + } else { + + // Supports user defined track event id + trackEvent._id = trackEvent.id || trackEvent._id || Popcorn.guid( trackEvent._natives.type ); + + if ( trackEvent._natives && trackEvent._natives._setup ) { + + trackEvent._natives._setup.call( this, trackEvent ); + + this.emit( "tracksetup", Popcorn.extend( {}, trackEvent, { + plugin: trackEvent._natives.type, + type: "tracksetup", + track: trackEvent + })); + } + + this.data.trackEvents.add( trackEvent ); + TrackEvent.start( this, trackEvent ); + + this.timeUpdate( this, null, true ); + + // Store references to user added trackevents in ref table + Popcorn.addTrackEvent.ref( this, trackEvent ); + } + + // Fire an event with change information + this.emit( "trackchange", { + id: trackEvent.id, + type: "trackchange", + previousValue: previousOpts, + currentValue: trackEvent, + track: trackEvent + }); + + return this; + } + + if ( trackEvent._natives.type !== "cue" ) { + // Fire an event with change information + this.emit( "trackchange", { + id: trackEvent.id, + type: "trackchange", + previousValue: previousOpts, + currentValue: newOpts, + track: trackEvent + }); + } + + return this; + } + } + + this.data.running[ name ] = this.data.running[ name ] || []; + + // Merge with defaults if they exist, make sure per call is prioritized + defaults = ( this.options.defaults && this.options.defaults[ name ] ) || {}; + mergedSetupOpts = Popcorn.extend( {}, defaults, options ); + + pluginFn.call( this, isfn ? definition.call( this, mergedSetupOpts ) : definition, + mergedSetupOpts ); + + return this; + }; + + // if the manifest parameter exists we should extend it onto the definition object + // so that it shows up when calling Popcorn.registry and Popcorn.registryByName + if ( manifest ) { + Popcorn.extend( definition, { + manifest: manifest + }); + } + + // Push into the registry + var entry = { + fn: plugin[ name ], + definition: definition, + base: definition, + parents: [], + name: name + }; + Popcorn.registry.push( + Popcorn.extend( plugin, entry, { + type: name + }) + ); + Popcorn.registryByName[ name ] = entry; + + return plugin; + }; + + // Storage for plugin function errors + Popcorn.plugin.errors = []; + + // Returns wrapped plugin function + function safeTry( fn, pluginName ) { + return function() { + + // When Popcorn.plugin.debug is true, do not suppress errors + if ( Popcorn.plugin.debug ) { + return fn.apply( this, arguments ); + } + + try { + return fn.apply( this, arguments ); + } catch ( ex ) { + + // Push plugin function errors into logging queue + Popcorn.plugin.errors.push({ + plugin: pluginName, + thrown: ex, + source: fn.toString() + }); + + // Trigger an error that the instance can listen for + // and react to + this.emit( "pluginerror", Popcorn.plugin.errors ); + } + }; + } + + // Debug-mode flag for plugin development + // True for Popcorn development versions, false for stable/tagged versions + Popcorn.plugin.debug = ( Popcorn.version === "@" + "VERSION" ); + + // removePlugin( type ) removes all tracks of that from all instances of popcorn + // removePlugin( obj, type ) removes all tracks of type from obj, where obj is a single instance of popcorn + Popcorn.removePlugin = function( obj, name ) { + + // Check if we are removing plugin from an instance or from all of Popcorn + if ( !name ) { + + // Fix the order + name = obj; + obj = Popcorn.p; + + if ( Popcorn.protect.natives.indexOf( name.toLowerCase() ) >= 0 ) { + Popcorn.error( "'" + name + "' is a protected function name" ); + return; + } + + var registryLen = Popcorn.registry.length, + registryIdx; + + // remove plugin reference from registry + for ( registryIdx = 0; registryIdx < registryLen; registryIdx++ ) { + if ( Popcorn.registry[ registryIdx ].name === name ) { + Popcorn.registry.splice( registryIdx, 1 ); + delete Popcorn.registryByName[ name ]; + delete Popcorn.manifest[ name ]; + + // delete the plugin + delete obj[ name ]; + + // plugin found and removed, stop checking, we are done + return; + } + } + + } + + var byStart = obj.data.trackEvents.byStart, + byEnd = obj.data.trackEvents.byEnd, + animating = obj.data.trackEvents.animating, + idx, sl; + + // remove all trackEvents + for ( idx = 0, sl = byStart.length; idx < sl; idx++ ) { + + if ( byStart[ idx ] && byStart[ idx ]._natives && byStart[ idx ]._natives.type === name ) { + + byStart[ idx ]._natives._teardown && byStart[ idx ]._natives._teardown.call( obj, byStart[ idx ] ); + + byStart.splice( idx, 1 ); + + // update for loop if something removed, but keep checking + idx--; sl--; + if ( obj.data.trackEvents.startIndex <= idx ) { + obj.data.trackEvents.startIndex--; + obj.data.trackEvents.endIndex--; + } + } + + // clean any remaining references in the end index + // we do this seperate from the above check because they might not be in the same order + if ( byEnd[ idx ] && byEnd[ idx ]._natives && byEnd[ idx ]._natives.type === name ) { + + byEnd.splice( idx, 1 ); + } + } + + //remove all animating events + for ( idx = 0, sl = animating.length; idx < sl; idx++ ) { + + if ( animating[ idx ] && animating[ idx ]._natives && animating[ idx ]._natives.type === name ) { + + animating.splice( idx, 1 ); + + // update for loop if something removed, but keep checking + idx--; sl--; + } + } + + }; + + Popcorn.compositions = {}; + + // Plugin inheritance + Popcorn.compose = function( name, definition, manifest ) { + + // If `manifest` arg is undefined, check for manifest within the `definition` object + // If no `definition.manifest`, an empty object is a sufficient fallback + Popcorn.manifest[ name ] = manifest = manifest || definition.manifest || {}; + + // register the effect by name + Popcorn.compositions[ name ] = definition; + }; + + Popcorn.plugin.effect = Popcorn.effect = Popcorn.compose; + + var rnaiveExpr = /^(?:\.|#|\[)/; + + // Basic DOM utilities and helpers API. See #1037 + Popcorn.dom = { + debug: false, + // Popcorn.dom.find( selector, context ) + // + // Returns the first element that matches the specified selector + // Optionally provide a context element, defaults to `document` + // + // eg. + // Popcorn.dom.find("video") returns the first video element + // Popcorn.dom.find("#foo") returns the first element with `id="foo"` + // Popcorn.dom.find("foo") returns the first element with `id="foo"` + // Note: Popcorn.dom.find("foo") is the only allowed deviation + // from valid querySelector selector syntax + // + // Popcorn.dom.find(".baz") returns the first element with `class="baz"` + // Popcorn.dom.find("[preload]") returns the first element with `preload="..."` + // ... + // See https://developer.mozilla.org/En/DOM/Document.querySelector + // + // + find: function( selector, context ) { + var node = null; + + // Default context is the `document` + context = context || document; + + if ( selector ) { + + // If the selector does not begin with "#", "." or "[", + // it could be either a nodeName or ID w/o "#" + if ( !rnaiveExpr.test( selector ) ) { + + // Try finding an element that matches by ID first + node = document.getElementById( selector ); + + // If a match was found by ID, return the element + if ( node !== null ) { + return node; + } + } + // Assume no elements have been found yet + // Catch any invalid selector syntax errors and bury them. + try { + node = context.querySelector( selector ); + } catch ( e ) { + if ( Popcorn.dom.debug ) { + throw new Error(e); + } + } + } + return node; + } + }; + + // Cache references to reused RegExps + var rparams = /\?/, + // XHR Setup object + setup = { + ajax: null, + url: "", + data: "", + dataType: "", + success: Popcorn.nop, + type: "GET", + async: true, + contentType: "application/x-www-form-urlencoded; charset=UTF-8" + }; + + Popcorn.xhr = function( options ) { + var settings; + + options.dataType = options.dataType && options.dataType.toLowerCase() || null; + + if ( options.dataType && + ( options.dataType === "jsonp" || options.dataType === "script" ) ) { + + Popcorn.xhr.getJSONP( + options.url, + options.success, + options.dataType === "script" + ); + return; + } + + // Merge the "setup" defaults and custom "options" + // into a new plain object. + settings = Popcorn.extend( {}, setup, options ); + + // Create new XMLHttpRequest object + settings.ajax = new XMLHttpRequest(); + + if ( settings.ajax ) { + + if ( settings.type === "GET" && settings.data ) { + + // append query string + settings.url += ( rparams.test( settings.url ) ? "&" : "?" ) + settings.data; + + // Garbage collect and reset settings.data + settings.data = null; + } + + // Open the request + settings.ajax.open( settings.type, settings.url, settings.async ); + + // For POST, set the content-type request header + if ( settings.type === "POST" ) { + settings.ajax.setRequestHeader( + "Content-Type", settings.contentType + ); + } + + settings.ajax.send( settings.data || null ); + + return Popcorn.xhr.httpData( settings ); + } + }; + + + Popcorn.xhr.httpData = function( settings ) { + + var data, json = null, + parser, xml = null; + + settings.ajax.onreadystatechange = function() { + + if ( settings.ajax.readyState === 4 ) { + + try { + json = JSON.parse( settings.ajax.responseText ); + } catch( e ) { + //suppress + } + + data = { + xml: settings.ajax.responseXML, + text: settings.ajax.responseText, + json: json + }; + + // Normalize: data.xml is non-null in IE9 regardless of if response is valid xml + if ( !data.xml || !data.xml.documentElement ) { + data.xml = null; + + try { + parser = new DOMParser(); + xml = parser.parseFromString( settings.ajax.responseText, "text/xml" ); + + if ( !xml.getElementsByTagName( "parsererror" ).length ) { + data.xml = xml; + } + } catch ( e ) { + // data.xml remains null + } + } + + // If a dataType was specified, return that type of data + if ( settings.dataType ) { + data = data[ settings.dataType ]; + } + + + settings.success.call( settings.ajax, data ); + + } + }; + return data; + }; + + Popcorn.xhr.getJSONP = function( url, success, isScript ) { + + var head = document.head || document.getElementsByTagName( "head" )[ 0 ] || document.documentElement, + script = document.createElement( "script" ), + isFired = false, + params = [], + rjsonp = /(=)\?(?=&|$)|\?\?/, + replaceInUrl, prefix, paramStr, callback, callparam; + + if ( !isScript ) { + + // is there a calback already in the url + callparam = url.match( /(callback=[^&]*)/ ); + + if ( callparam !== null && callparam.length ) { + + prefix = callparam[ 1 ].split( "=" )[ 1 ]; + + // Since we need to support developer specified callbacks + // and placeholders in harmony, make sure matches to "callback=" + // aren't just placeholders. + // We coded ourselves into a corner here. + // JSONP callbacks should never have been + // allowed to have developer specified callbacks + if ( prefix === "?" ) { + prefix = "jsonp"; + } + + // get the callback name + callback = Popcorn.guid( prefix ); + + // replace existing callback name with unique callback name + url = url.replace( /(callback=[^&]*)/, "callback=" + callback ); + } else { + + callback = Popcorn.guid( "jsonp" ); + + if ( rjsonp.test( url ) ) { + url = url.replace( rjsonp, "$1" + callback ); + } + + // split on first question mark, + // this is to capture the query string + params = url.split( /\?(.+)?/ ); + + // rebuild url with callback + url = params[ 0 ] + "?"; + if ( params[ 1 ] ) { + url += params[ 1 ] + "&"; + } + url += "callback=" + callback; + } + + // Define the JSONP success callback globally + window[ callback ] = function( data ) { + // Fire success callbacks + success && success( data ); + isFired = true; + }; + } + + script.addEventListener( "load", function() { + + // Handling remote script loading callbacks + if ( isScript ) { + // getScript + success && success(); + } + + // Executing for JSONP requests + if ( isFired ) { + // Garbage collect the callback + delete window[ callback ]; + } + // Garbage collect the script resource + head.removeChild( script ); + }, false ); + + script.addEventListener( "error", function( e ) { + // Handling remote script loading callbacks + success && success( { error: e } ); + + // Executing for JSONP requests + if ( !isScript ) { + // Garbage collect the callback + delete window[ callback ]; + } + // Garbage collect the script resource + head.removeChild( script ); + }, false ); + + script.src = url; + head.insertBefore( script, head.firstChild ); + + return; + }; + + Popcorn.getJSONP = Popcorn.xhr.getJSONP; + + Popcorn.getScript = Popcorn.xhr.getScript = function( url, success ) { + + return Popcorn.xhr.getJSONP( url, success, true ); + }; + + Popcorn.util = { + // Simple function to parse a timestamp into seconds + // Acceptable formats are: + // HH:MM:SS.MMM + // HH:MM:SS;FF + // Hours and minutes are optional. They default to 0 + toSeconds: function( timeStr, framerate ) { + // Hours and minutes are optional + // Seconds must be specified + // Seconds can be followed by milliseconds OR by the frame information + var validTimeFormat = /^([0-9]+:){0,2}[0-9]+([.;][0-9]+)?$/, + errorMessage = "Invalid time format", + digitPairs, lastIndex, lastPair, firstPair, + frameInfo, frameTime; + + if ( typeof timeStr === "number" ) { + return timeStr; + } + + if ( typeof timeStr === "string" && + !validTimeFormat.test( timeStr ) ) { + Popcorn.error( errorMessage ); + } + + digitPairs = timeStr.split( ":" ); + lastIndex = digitPairs.length - 1; + lastPair = digitPairs[ lastIndex ]; + + // Fix last element: + if ( lastPair.indexOf( ";" ) > -1 ) { + + frameInfo = lastPair.split( ";" ); + frameTime = 0; + + if ( framerate && ( typeof framerate === "number" ) ) { + frameTime = parseFloat( frameInfo[ 1 ], 10 ) / framerate; + } + + digitPairs[ lastIndex ] = parseInt( frameInfo[ 0 ], 10 ) + frameTime; + } + + firstPair = digitPairs[ 0 ]; + + return { + + 1: parseFloat( firstPair, 10 ), + + 2: ( parseInt( firstPair, 10 ) * 60 ) + + parseFloat( digitPairs[ 1 ], 10 ), + + 3: ( parseInt( firstPair, 10 ) * 3600 ) + + ( parseInt( digitPairs[ 1 ], 10 ) * 60 ) + + parseFloat( digitPairs[ 2 ], 10 ) + + }[ digitPairs.length || 1 ]; + } + }; + + // alias for exec function + Popcorn.p.cue = Popcorn.p.exec; + + // Protected API methods + Popcorn.protect = { + natives: getKeys( Popcorn.p ).map(function( val ) { + return val.toLowerCase(); + }) + }; + + // Setup logging for deprecated methods + Popcorn.forEach({ + // Deprecated: Recommended + "listen": "on", + "unlisten": "off", + "trigger": "emit", + "exec": "cue" + + }, function( recommend, api ) { + var original = Popcorn.p[ api ]; + // Override the deprecated api method with a method of the same name + // that logs a warning and defers to the new recommended method + Popcorn.p[ api ] = function() { + if ( typeof console !== "undefined" && console.warn ) { + console.warn( + "Deprecated method '" + api + "', " + + (recommend == null ? "do not use." : "use '" + recommend + "' instead." ) + ); + + // Restore api after first warning + Popcorn.p[ api ] = original; + } + return Popcorn.p[ recommend ].apply( this, [].slice.call( arguments ) ); + }; + }); + + + // Exposes Popcorn to global context + global.Popcorn = Popcorn; + +})(window, window.document); diff --git a/lib/popcorn.player.js b/lib/popcorn.player.js new file mode 100644 index 00000000..7e154f4f --- /dev/null +++ b/lib/popcorn.player.js @@ -0,0 +1,437 @@ +(function( Popcorn ) { + + // combines calls of two function calls into one + var combineFn = function( first, second ) { + + first = first || Popcorn.nop; + second = second || Popcorn.nop; + + return function() { + + first.apply( this, arguments ); + second.apply( this, arguments ); + }; + }; + + // ID string matching + var rIdExp = /^(#([\w\-\_\.]+))$/; + + Popcorn.player = function( name, player ) { + + // return early if a player already exists under this name + if ( Popcorn[ name ] ) { + + return; + } + + player = player || {}; + + var playerFn = function( target, src, options ) { + + options = options || {}; + + // List of events + var date = new Date() / 1000, + baselineTime = date, + currentTime = 0, + readyState = 0, + volume = 1, + muted = false, + events = {}, + + // The container div of the resource + container = typeof target === "string" ? Popcorn.dom.find( target ) : target, + basePlayer = {}, + timeout, + popcorn; + + if ( !Object.prototype.__defineGetter__ ) { + + basePlayer = container || document.createElement( "div" ); + } + + // copies a div into the media object + for( var val in container ) { + + // don't copy properties if using container as baseplayer + if ( val in basePlayer ) { + + continue; + } + + if ( typeof container[ val ] === "object" ) { + + basePlayer[ val ] = container[ val ]; + } else if ( typeof container[ val ] === "function" ) { + + basePlayer[ val ] = (function( value ) { + + // this is a stupid ugly kludgy hack in honour of Safari + // in Safari a NodeList is a function, not an object + if ( "length" in container[ value ] && !container[ value ].call ) { + + return container[ value ]; + } else { + + return function() { + + return container[ value ].apply( container, arguments ); + }; + } + }( val )); + } else { + + Popcorn.player.defineProperty( basePlayer, val, { + get: (function( value ) { + + return function() { + + return container[ value ]; + }; + }( val )), + set: Popcorn.nop, + configurable: true + }); + } + } + + var timeupdate = function() { + + date = new Date() / 1000; + + if ( !basePlayer.paused ) { + + basePlayer.currentTime = basePlayer.currentTime + ( date - baselineTime ); + basePlayer.dispatchEvent( "timeupdate" ); + timeout = setTimeout( timeupdate, 10 ); + } + + baselineTime = date; + }; + + basePlayer.play = function() { + + this.paused = false; + + if ( basePlayer.readyState >= 4 ) { + + baselineTime = new Date() / 1000; + basePlayer.dispatchEvent( "play" ); + timeupdate(); + } + }; + + basePlayer.pause = function() { + + this.paused = true; + basePlayer.dispatchEvent( "pause" ); + }; + + Popcorn.player.defineProperty( basePlayer, "currentTime", { + get: function() { + + return currentTime; + }, + set: function( val ) { + + // make sure val is a number + currentTime = +val; + basePlayer.dispatchEvent( "timeupdate" ); + + return currentTime; + }, + configurable: true + }); + + Popcorn.player.defineProperty( basePlayer, "volume", { + get: function() { + + return volume; + }, + set: function( val ) { + + // make sure val is a number + volume = +val; + basePlayer.dispatchEvent( "volumechange" ); + return volume; + }, + configurable: true + }); + + Popcorn.player.defineProperty( basePlayer, "muted", { + get: function() { + + return muted; + }, + set: function( val ) { + + // make sure val is a number + muted = +val; + basePlayer.dispatchEvent( "volumechange" ); + return muted; + }, + configurable: true + }); + + Popcorn.player.defineProperty( basePlayer, "readyState", { + get: function() { + + return readyState; + }, + set: function( val ) { + + readyState = val; + return readyState; + }, + configurable: true + }); + + // Adds an event listener to the object + basePlayer.addEventListener = function( evtName, fn ) { + + if ( !events[ evtName ] ) { + + events[ evtName ] = []; + } + + events[ evtName ].push( fn ); + return fn; + }; + + // Removes an event listener from the object + basePlayer.removeEventListener = function( evtName, fn ) { + + var i, + listeners = events[ evtName ]; + + if ( !listeners ){ + + return; + } + + // walk backwards so we can safely splice + for ( i = events[ evtName ].length - 1; i >= 0; i-- ) { + + if( fn === listeners[ i ] ) { + + listeners.splice(i, 1); + } + } + + return fn; + }; + + // Can take event object or simple string + basePlayer.dispatchEvent = function( oEvent ) { + + var evt, + self = this, + eventInterface, + eventName = oEvent.type; + + // A string was passed, create event object + if ( !eventName ) { + + eventName = oEvent; + eventInterface = Popcorn.events.getInterface( eventName ); + + if ( eventInterface ) { + + evt = document.createEvent( eventInterface ); + evt.initEvent( eventName, true, true, window, 1 ); + } + } + + if ( events[ eventName ] ) { + + for ( var i = events[ eventName ].length - 1; i >= 0; i-- ) { + + events[ eventName ][ i ].call( self, evt, self ); + } + } + }; + + // Attempt to get src from playerFn parameter + basePlayer.src = src || ""; + basePlayer.duration = 0; + basePlayer.paused = true; + basePlayer.ended = 0; + + options && options.events && Popcorn.forEach( options.events, function( val, key ) { + + basePlayer.addEventListener( key, val, false ); + }); + + // true and undefined returns on canPlayType means we should attempt to use it, + // false means we cannot play this type + if ( player._canPlayType( container.nodeName, src ) !== false ) { + + if ( player._setup ) { + + player._setup.call( basePlayer, options ); + } else { + + // there is no setup, which means there is nothing to load + basePlayer.readyState = 4; + basePlayer.dispatchEvent( "loadedmetadata" ); + basePlayer.dispatchEvent( "loadeddata" ); + basePlayer.dispatchEvent( "canplaythrough" ); + } + } else { + + // Asynchronous so that users can catch this event + setTimeout( function() { + basePlayer.dispatchEvent( "error" ); + }, 0 ); + } + + popcorn = new Popcorn.p.init( basePlayer, options ); + + if ( player._teardown ) { + + popcorn.destroy = combineFn( popcorn.destroy, function() { + + player._teardown.call( basePlayer, options ); + }); + } + + return popcorn; + }; + + playerFn.canPlayType = player._canPlayType = player._canPlayType || Popcorn.nop; + + Popcorn[ name ] = Popcorn.player.registry[ name ] = playerFn; + }; + + Popcorn.player.registry = {}; + + Popcorn.player.defineProperty = Object.defineProperty || function( object, description, options ) { + + object.__defineGetter__( description, options.get || Popcorn.nop ); + object.__defineSetter__( description, options.set || Popcorn.nop ); + }; + + // player queue is to help players queue things like play and pause + // HTML5 video's play and pause are asynch, but do fire in sequence + // play() should really mean "requestPlay()" or "queuePlay()" and + // stash a callback that will play the media resource when it's ready to be played + Popcorn.player.playerQueue = function() { + + var _queue = [], + _running = false; + + return { + next: function() { + + _running = false; + _queue.shift(); + _queue[ 0 ] && _queue[ 0 ](); + }, + add: function( callback ) { + + _queue.push(function() { + + _running = true; + callback && callback(); + }); + + // if there is only one item on the queue, start it + !_running && _queue[ 0 ](); + } + }; + }; + + // Popcorn.smart will attempt to find you a wrapper or player. If it can't do that, + // it will default to using an HTML5 video in the target. + Popcorn.smart = function( target, src, options ) { + var node = typeof target === "string" ? Popcorn.dom.find( target ) : target, + i, srci, j, media, mediaWrapper, popcorn, srcLength, + // We leave HTMLVideoElement and HTMLAudioElement wrappers out + // of the mix, since we'll default to HTML5 video if nothing + // else works. Waiting on #1254 before we add YouTube to this. + wrappers = "HTMLYouTubeVideoElement HTMLVimeoVideoElement HTMLSoundCloudAudioElement HTMLNullVideoElement".split(" "); + + if ( !node ) { + Popcorn.error( "Specified target `" + target + "` was not found." ); + return; + } + + // If our src is not an array, create an array of one. + src = typeof src === "string" ? [ src ] : src; + + // Loop through each src, and find the first playable. + for ( i = 0, srcLength = src.length; i < srcLength; i++ ) { + srci = src[ i ]; + + // See if we can use a wrapper directly, if not, try players. + for ( j = 0; j < wrappers.length; j++ ) { + mediaWrapper = Popcorn[ wrappers[ j ] ]; + if ( mediaWrapper && mediaWrapper._canPlaySrc( srci ) === "probably" ) { + media = mediaWrapper( node ); + popcorn = Popcorn( media, options ); + // Set src, but not until after we return the media so the caller + // can get error events, if any. + setTimeout( function() { + media.src = srci; + }, 0 ); + return popcorn; + } + } + + // No wrapper can play this, check players. + for ( var key in Popcorn.player.registry ) { + if ( Popcorn.player.registry.hasOwnProperty( key ) ) { + if ( Popcorn.player.registry[ key ].canPlayType( node.nodeName, srci ) ) { + // Popcorn.smart( player, src, /* options */ ) + return Popcorn[ key ]( node, srci, options ); + } + } + } + } + + // If we don't have any players or wrappers that can handle this, + // Default to using HTML5 video. Similar to the HTMLVideoElement + // wrapper, we put a video in the div passed to us via: + // Popcorn.smart( div, src, options ) + var videoHTML, + videoElement, + videoID = Popcorn.guid( "popcorn-video-" ), + videoHTMLContainer = document.createElement( "div" ); + + videoHTMLContainer.style.width = "100%"; + videoHTMLContainer.style.height = "100%"; + + // If we only have one source, do not bother with source elements. + // This means we don't have the IE9 hack, + // and we can properly listen to error events. + // That way an error event can be told to backup to Flash if it fails. + if ( src.length === 1 ) { + videoElement = document.createElement( "video" ); + videoElement.id = videoID; + node.appendChild( videoElement ); + setTimeout( function() { + // Hack to decode html characters like & to & + var decodeDiv = document.createElement( "div" ); + decodeDiv.innerHTML = src[ 0 ]; + + videoElement.src = decodeDiv.firstChild.nodeValue; + }, 0 ); + return Popcorn( '#' + videoID, options ); + } + + node.appendChild( videoHTMLContainer ); + // IE9 doesn't like dynamic creation of source elements on