From abe9252bbb7d255dd4671cf0afd93d980c2bf6df Mon Sep 17 00:00:00 2001 From: Steve Heffernan Date: Mon, 17 May 2010 19:51:17 -0700 Subject: [PATCH] First version. --- class.js | 33 +++++ demo.html | 27 ++++ video-js.css | 44 ++++++ video.js | 378 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 482 insertions(+) create mode 100644 class.js create mode 100644 demo.html create mode 100644 video-js.css create mode 100644 video.js diff --git a/class.js b/class.js new file mode 100644 index 0000000000..7eab489caf --- /dev/null +++ b/class.js @@ -0,0 +1,33 @@ +// jresig's Class implementation +(function(){ + var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; + this.Class = function(){}; + Class.extend = function(prop) { + var _super = this.prototype; + initializing = true; + var prototype = new this(); + initializing = false; + for (var name in prop) { + prototype[name] = typeof prop[name] == "function" && + typeof _super[name] == "function" && fnTest.test(prop[name]) ? + (function(name, fn){ + return function() { + var tmp = this._super; + this._super = _super[name]; + var ret = fn.apply(this, arguments); + this._super = tmp; + return ret; + }; + })(name, prop[name]) : + prop[name]; + } + function Class() { + if ( !initializing && this.init ) + this.init.apply(this, arguments); + } + Class.prototype = prototype; + Class.constructor = Class; + Class.extend = arguments.callee; + return Class; + }; +})(); \ No newline at end of file diff --git a/demo.html b/demo.html new file mode 100644 index 0000000000..7963133600 --- /dev/null +++ b/demo.html @@ -0,0 +1,27 @@ + + + + + HTML5 Video Player + + + + + + +
+ +
+ + + \ No newline at end of file diff --git a/video-js.css b/video-js.css new file mode 100644 index 0000000000..178ddea964 --- /dev/null +++ b/video-js.css @@ -0,0 +1,44 @@ +body { background-color: #222; color: #fff; } +.video-box { text-align: left; position: relative; } +.video-js { background-color: #000; } + +/* General controls styles */ +.vjs-controls { display: none; list-style: none; margin: 0; padding: 0; position: absolute; height: 30px; opacity: 0.85; color: #fff; } +.vjs-controls > li { list-style: none; float: left; height: 25px; width: 25px; margin: 0 5px 0 0; padding: 0; background-color: #001E25; text-align: center; -webkit-border-radius: 5px; -moz-border-radius: 5px; border-radius: 5px; } +.vjs-controls > li:last-child { margin-right: 0; } +.vjs-controls > li:first-child { margin-left: 5px; } + +/* Play/Pause */ +.vjs-play-control span { display: block; font-size: 0px; line-height: 0; text-decoration: none; } +.vjs-play-control.vjs-play span { width: 0; height: 0; margin: 8px 0 0 8px; border-top: 5px solid #001E25; border-left: 10px solid #fff; border-bottom: 5px solid #001E25; } +.vjs-play-control.vjs-pause span { width: 3px; height: 10px; margin: 8px auto 0; border-top: 0px; border-left: 3px solid #fff; border-bottom: 0px; border-right: 3px solid #fff; } + +/* Progress */ +.vjs-progress-control { width: 190px; } +.vjs-progress-control ul { list-style: none; margin: 0; padding: 0; } +.vjs-progress-control .vjs-progress-holder { list-style: none; position: relative; float: left; width: 100px; height: 9px; border: 1px solid #777; margin: 7px 0 0 5px; padding: 0; background-color: #001E25; overflow:hidden; -webkit-border-radius: 5px; -moz-border-radius: 5px; border-radius: 5px; } +.vjs-progress-control .vjs-play-progress { position: absolute; display: block; width: 0px; height: 9px; background-color: #fff; -webkit-border-radius: 5px; -moz-border-radius: 5px; border-radius: 5px; } +.vjs-progress-control .vjs-load-progress { position: absolute; display: block; width: 0px; height: 9px; background-color: #777; -webkit-border-radius: 5px; -moz-border-radius: 5px; border-radius: 5px; } +.vjs-progress-control .vjs-progress-time { list-style: none; float: left; margin: 7px 0 0 5px; padding: 0; font-size: 10px; line-height: 1; font-weight: normal; font-family: Helvetica, Arial, sans-serif; } + +/* Volume */ +.vjs-volume-control { width: 50px !important; } +.vjs-volume-control ul { display: block; margin: 0; padding: 4px 0 0 5px; list-style: none; } +.vjs-volume-control ul li { float: left; margin: 0; padding: 0; list-style: none; width: 5px; margin-right: 2px; height: 0px; border-bottom: 18px solid #555; } +.vjs-volume-control ul li:nth-child(1) { border-bottom-width: 2px; height: 16px; } +.vjs-volume-control ul li:nth-child(2) { border-bottom-width: 4px; height: 14px; } +.vjs-volume-control ul li:nth-child(3) { border-bottom-width: 7px; height: 11px; } +.vjs-volume-control ul li:nth-child(4) { border-bottom-width: 10px; height: 8px; } +.vjs-volume-control ul li:nth-child(5) { border-bottom-width: 14px; height: 4px; } + +/* Fullscreen */ +.vjs-fullscreen-control ul { list-style: none; margin: 5px 0 0 5px; padding: 0; width: 20px; height: 20px; text-align: left; vertical-align: top; } +.vjs-fullscreen-control ul li { list-style: none; float: left; margin: 0; padding: 0; font-size: 0; line-height: 0; width: 0; text-align: left; vertical-align: top; } +.vjs-fullscreen-control ul li:nth-child(1) { margin: 0 3px 3px 0; border: none; border-top: 6px solid #fff; border-right: 6px solid #001E25; } +.vjs-fullscreen-control ul li:nth-child(2) { border: none; border-top: 6px solid #fff; border-left: 6px solid #001E25; } +.vjs-fullscreen-control ul li:nth-child(3) { clear: both; margin: 0 3px 0 0; border: none; border-bottom: 6px solid #fff; border-right: 6px solid #001E25; } +.vjs-fullscreen-control ul li:nth-child(4) { border: none; border-bottom: 6px solid #fff; border-left: 6px solid #001E25; } +.vjs-fullscreen-control.vjs-fs-active ul li:nth-child(1) { border: none; border-bottom: 6px solid #fff; border-left: 6px solid #001E25; } +.vjs-fullscreen-control.vjs-fs-active ul li:nth-child(2) { border: none; border-bottom: 6px solid #fff; border-right: 6px solid #001E25; } +.vjs-fullscreen-control.vjs-fs-active ul li:nth-child(3) { border: none; border-top: 6px solid #fff; border-left: 6px solid #001E25; } +.vjs-fullscreen-control.vjs-fs-active ul li:nth-child(4) { border: none; border-top: 6px solid #fff; border-right: 6px solid #001E25; } \ No newline at end of file diff --git a/video.js b/video.js new file mode 100644 index 0000000000..31cb28f8fc --- /dev/null +++ b/video.js @@ -0,0 +1,378 @@ +// Using jresig's Class implementation http://ejohn.org/blog/simple-javascript-inheritance/ +(function(){var initializing=false, fnTest=/xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; this.Class = function(){}; Class.extend = function(prop) { var _super = this.prototype; initializing = true; var prototype = new this(); initializing = false; for (var name in prop) { prototype[name] = typeof prop[name] == "function" && typeof _super[name] == "function" && fnTest.test(prop[name]) ? (function(name, fn){ return function() { var tmp = this._super; this._super = _super[name]; var ret = fn.apply(this, arguments); this._super = tmp; return ret; }; })(name, prop[name]) : prop[name]; } function Class() { if ( !initializing && this.init ) this.init.apply(this, arguments); } Class.prototype = prototype; Class.constructor = Class; Class.extend = arguments.callee; return Class;};})(); + +// Store a list of players on the page for reference by event listeners +var videoJSPlayers = new Array(); + +// Video JS Class +var VideoJS = Class.extend({ + + // Initialize the player for the supplied video tag element + // element: video tag + // num: the current player's position in the videoJSPlayers array + init: function(element, num){ + this.video = element; + + // Default the player number to 0 + this.num = (num) ? num : 0; + + this.buildController(); + this.showController(); // has to come before positioning + this.positionController(); + + // Listen for clicks on the play/pause button + this.playControl.addEventListener("click", this.onPlayControlClick, true); + + // Listen for drags on the progress bar + this.progressHolder.addEventListener("mousedown", this.onProgressHolderMouseDown, true); + // Listen for a release on the progress bar + this.progressHolder.addEventListener("mouseup", this.onProgressHolderMouseUp, true); + + // Listen for a drag on the volume control + this.volumeControl.addEventListener("mousedown", this.onVolumeControlMouseDown, true); + // Listen for a release on the volume control + this.volumeControl.addEventListener("mouseup", this.onVolumeControlMouseUp, true); + // Set the display to the initial volume + this.updateVolumeDisplay(); + + // Listen for clicks on the fullscreen button + this.fullscreenControl.addEventListener("click", this.onFullscreenControlClick, true); + }, + + buildController: function(){ + + /* Creating this HTML + + */ + + // Create a list element to hold the different controls + this.controls = document.createElement("ul"); + + // Add the controls to the video's container + this.video.parentNode.appendChild(this.controls); + this.controls.className = "vjs-controls"; + + // Store the current video player's number + // For referencing in event listeners + this.controls.setAttribute("data-video-js", this.num); + + // Build the play control + this.playControl = document.createElement("li"); + this.controls.appendChild(this.playControl); + this.playControl.className = "vjs-play-control vjs-play"; + this.playControl.innerHTML = ""; + + // Build the progress control + this.progressControl = document.createElement("li"); + this.controls.appendChild(this.progressControl); + this.progressControl.className = "vjs-progress-control"; + + // Create a list for the different progress elements + this.progressList = document.createElement("ul"); + this.progressControl.appendChild(this.progressList); + + // Create a holder for the progress bars + this.progressHolder = document.createElement("li"); + this.progressList.appendChild(this.progressHolder); + this.progressHolder.className = "vjs-progress-holder"; + + // Create the loading progress display + this.loadProgress = document.createElement("span"); + this.progressHolder.appendChild(this.loadProgress) + this.loadProgress.className = "vjs-load-progress"; + + // Create the playing progress display + this.playProgress = document.createElement("span"); + this.progressHolder.appendChild(this.playProgress); + this.playProgress.className = "vjs-play-progress"; + + // Create the progress time display (00:00 / 00:00) + this.progressTime = document.createElement("li"); + this.progressList.appendChild(this.progressTime); + this.progressTime.className = "vjs-progress-time"; + + // Create the current play time display + this.currentTimeDisplay = document.createElement("span"); + this.progressTime.appendChild(this.currentTimeDisplay); + this.currentTimeDisplay.className = "vjs-current-time-display"; + this.currentTimeDisplay.innerHTML = '00:00'; + + // Adding a slash for visual separation + this.progressTime.innerHTML += " / "; + + // Create the total duration display + this.durationDisplay = document.createElement("span"); + this.progressTime.appendChild(this.durationDisplay); + this.durationDisplay.className = "vjs-duration-display"; + this.durationDisplay.innerHTML = '00:00'; + + // Create the volumne control + this.volumeControl = document.createElement("li"); + this.controls.appendChild(this.volumeControl); + this.volumeControl.className = "vjs-volume-control"; + this.volumeControl.innerHTML = ""; + this.volumeDisplay = this.volumeControl.children[0] + + // Crete the fullscreen control + this.fullscreenControl = document.createElement("li"); + this.controls.appendChild(this.fullscreenControl); + this.fullscreenControl.className = "vjs-fullscreen-control"; + this.fullscreenControl.innerHTML = ""; + }, + + // Show the controller + showController: function(){ + this.controls.style.display = "block"; + }, + + // Place controller relative to the video's position + positionController: function(){ + this.controls.style.top = (this.video.offsetHeight - this.controls.offsetHeight) + "px"; + this.controls.style.left = "0px"; + this.controls.style.width = this.video.offsetWidth + "px"; + this.sizeProgressBar(); + }, + + // Hide the controller + hideController: function(){ + this.controls.style.display = "none"; + }, + + // React to clicks on the play/pause button + onPlayControlClick: function(e){ + var player = videoJSPlayers[this.parentNode.getAttribute("data-video-js")]; + if (player.video.paused) { + player.playVideo(); + } else { + player.pauseVideo(); + } + }, + + // Adjust the play position when the user drags on the progress bar + onProgressHolderMouseDown: function(e){ + var player = videoJSPlayers[this.parentNode.parentNode.parentNode.getAttribute("data-video-js")]; + player.stopTrackingPlayProgress(); + + if (player.video.paused) { + player.videoWasPlaying = false; + } else { + player.videoWasPlaying = true; + player.video.pause(); + } + + player.blockTextSelection(); + document.onmousemove = function(e) { + player.setPlayProgress(e.pageX); + } + + document.onmouseup = function() { + player.unblockTextSelection(); + document.onmousemove = null; + document.onmouseup = null; + if (player.videoWasPlaying) { + player.video.play(); + player.trackPlayProgress(); + } + } + }, + + // When the user stops dragging on the progress bar, update play position + // Backup for when the user only clicks and doesn't drag + onProgressHolderMouseUp: function(e){ + var player = videoJSPlayers[this.parentNode.parentNode.parentNode.getAttribute("data-video-js")]; + player.setPlayProgress(e.pageX); + }, + + // Adjust the volume when the user drags on the volume control + onVolumeControlMouseDown: function(e){ + var player = videoJSPlayers[this.parentNode.getAttribute("data-video-js")]; + player.blockTextSelection(); + document.onmousemove = function(e) { + player.setVolume(e.pageX); + } + document.onmouseup = function() { + player.unblockTextSelection(); + document.onmousemove = null; + document.onmouseup = null; + } + }, + + // When the user stops dragging, set a new volume + // Backup for when the user only clicks and doesn't drag + onVolumeControlMouseUp: function(e){ + var player = videoJSPlayers[this.parentNode.getAttribute("data-video-js")]; + player.setVolume(e.pageX); + }, + + // When the user clicks on the fullscreen button, update fullscreen setting + onFullscreenControlClick: function(e){ + var player = videoJSPlayers[this.parentNode.getAttribute("data-video-js")]; + if (!player.videoIsFullScreen) { + player.fullscreenOn(); + } else { + player.fullscreenOff(); + } + }, + + // Play the video + playVideo: function(){ + this.video.play(); + this.playControl.className = "vjs-play-control vjs-pause"; + this.trackPlayProgress(); + }, + + // Pause the video + pauseVideo: function(){ + this.video.pause(); + this.playControl.className = "vjs-play-control vjs-play"; + this.stopTrackingPlayProgress(); + }, + + // Adjust the width of the progress bar to fill the controls width + sizeProgressBar: function(){ + this.progressControl.style.width = (this.controls.offsetWidth - 125) + "px"; + this.progressHolder.style.width = (this.progressControl.offsetWidth - 80) + "px"; + this.updatePlayProgress(); + }, + + // Track & display the current play progress + trackPlayProgress: function(){ + context = this; + this.playProgressInterval = setInterval(function(){ context.updatePlayProgress(); }, 33); + }, + + // Turn off play progress tracking (when paused) + stopTrackingPlayProgress: function(){ + clearInterval(this.playProgressInterval); + }, + + // Ajust the play progress bar's width based on the current play time + updatePlayProgress: function(){ + this.playProgress.style.width = ((this.video.currentTime / this.video.duration) * (this.progressHolder.offsetWidth - 2)) + "px"; + this.updateTimeDisplay(); + }, + + // Update the play position based on where the user clicked on the progresss bar + setPlayProgress: function(clickX) { + var newPercent = Math.max(0, Math.min(1, (clickX - this.findPosX(this.progressHolder)) / this.progressHolder.offsetWidth)); + this.video.currentTime = newPercent * this.video.duration + this.playProgress.style.width = newPercent * (this.progressHolder.offsetWidth - 2) + "px"; + this.updateTimeDisplay(); + }, + + // Update the displayed time (00:00) + updateTimeDisplay: function(){ + this.currentTimeDisplay.innerHTML = this.formatTime(this.video.currentTime); + if (this.video.duration) this.durationDisplay.innerHTML = this.formatTime(this.video.duration); + }, + + // Set a new volume based on where the user clicked on the volume control + setVolume: function(clickX) { + var newVol = (clickX - this.findPosX(this.volumeControl)) / this.volumeControl.offsetWidth; + if (newVol > 1) { + newVol = 1; + } else if (newVol < 0) { + newVol = 0; + } + this.video.volume = newVol; + this.updateVolumeDisplay(); + }, + + // Update the volume control display + // Unique to these default controls. Uses borders to create the look of bars. + updateVolumeDisplay: function(){ + var volNum = Math.floor(this.video.volume * 6); + for(var i=0; i<6; i++) { + if (i < volNum) { + this.volumeDisplay.children[i].style.borderColor = "#fff"; + } else { + this.volumeDisplay.children[i].style.borderColor = "#555"; + } + } + }, + + // Turn on fullscreen (window) mode + // Real fullscreen isn't available in browsers quite yet. + fullscreenOn: function(){ + this.videoIsFullScreen = true; + this.videoOrigWidth = this.video.offsetWidth; + this.videoOrigHeight = this.video.offsetHeight; + + this.video.style.width = window.innerWidth + "px"; + this.video.style.height = window.innerHeight + "px"; + this.video.style.position = "fixed"; + this.video.style.left = 0; + this.video.style.top = 0; + this.controls.style.position = "fixed"; + this.positionController(); + + this.fullscreenControl.className = "vjs-fullscreen-control vjs-fs-active"; + }, + + // Turn off fullscreen (window) mode + fullscreenOff: function(){ + this.videoIsFullScreen = false; + this.video.style.width = this.videoOrigWidth + "px"; + this.video.style.height = this.videoOrigHeight + "px"; + this.video.style.position = "static"; + this.controls.style.position = "absolute"; + this.positionController(); + this.fullscreenControl.className = "vjs-fullscreen-control"; + }, + + // Attempt to block the ability to select text while dragging controls + blockTextSelection: function(){ + document.body.focus(); + document.onselectstart = function () { return false; }; + }, + + // Turn off text selection blocking + unblockTextSelection: function(){ + document.onselectstart = function () { return true; }; + }, + + // Return seconds as MM:SS + formatTime: function(seconds) { + seconds = Math.round(seconds); + minutes = Math.floor(seconds / 60); + minutes = (minutes >= 10) ? minutes : "0" + minutes; + seconds = Math.floor(seconds % 60); + seconds = (seconds >= 10) ? seconds : "0" + seconds; + return minutes + ":" + seconds; + }, + + // Get an objects position on the page + findPosX: function(obj) { + var curleft = obj.offsetLeft; + while(obj = obj.offsetParent) { + curleft += obj.offsetLeft; + } + return curleft; + } + +}) +