Commit 2d31a06d authored by David Loiret's avatar David Loiret

feat(remote playback): first implementation of player controls

parent 7aa8cc78
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>%%WEBINTF_TITLE%%</title>
<link rel="stylesheet" href="style.css">
<script type="text/javascript" src="jquery-1.10.1.min.js"></script>
<script type="text/javascript" src="jquery.ui.widget.js"></script>
<script type="text/javascript" src="jquery.iframe-transport.js"></script>
<script type="text/javascript" src="jquery.fileupload.js"></script>
<script type="text/javascript" src="main.js"></script>
</head>
<body>
<div id="header">
<div class="nav">
<a class="btn linkBtn" href="http://www.videolan.org" target="_blank"><div class="icon"></div></a>
<div class="btn uploadBtn">
<input id="fileupload" type="file" name="files[]" data-url="upload.json" multiple>
+
<head>
<meta charset="UTF-8">
<title>%%WEBINTF_TITLE%%</title>
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="playerControl.css">
<script type="text/javascript" src="jquery-1.10.1.min.js"></script>
<script type="text/javascript" src="jquery.ui.widget.js"></script>
<script type="text/javascript" src="jquery.iframe-transport.js"></script>
<script type="text/javascript" src="jquery.fileupload.js"></script>
<script type="text/javascript" src="main.js"></script>
<script type="text/javascript" src="playerControl.js"></script>
</head>
<body>
<div id="header">
<div class="nav">
<a class="btn linkBtn" href="http://www.videolan.org" target="_blank"><div class="icon"></div></a>
<div class="btn uploadBtn">
<input id="fileupload" type="file" name="files[]" data-url="upload.json" multiple>
+
</div>
</div>
</div>
</div>
</div>
<div class="main">
<div class="uploads">
<ul></ul>
</div>
<div class="messageUpload">
<h1>%%WEBINTF_DROPFILES%%</h1>
<p>%%WEBINTF_DROPFILES_LONG%%</p>
</div>
<div id="footer">
<p>VLC for tvOS is free, open-source software published by <a href="http://www.videolan.org" target="_blank">VideoLAN</a>. <br />Modification and redistribution is subject to both the <a href="http://opensource.org/licenses/GPL-2.0" target="_blank">GPLv2 (or later)</a> and the <a href="http://opensource.org/licenses/MPL-2.0" target="_blank">MPLv2</a> as well as further rights reserved by the VideoLAN non-profit organization.<br />You can find more information about licensing in the About dialog within the app.<br />VideoLAN, VLC and VLC media player are internationally registered trademarks of the <a href="http://www.videolan.org/videolan/" target="_blank">VideoLAN non-profit organization</a>.</p>
</div>
</div>
<div id="overlay" class="">
<div id="modal">
<ul class="downloads cf">
<!-- content of div.content goes here -->
</ul>
</div>
</div>
</body>
<div class="main">
<div class="uploads">
<ul></ul>
</div>
<div class="messageUpload">
<h1>%%WEBINTF_DROPFILES%%</h1>
<p>%%WEBINTF_DROPFILES_LONG%%</p>
</div>
<div class="player-control">
<div class="title"></div>
<div class="play-pause play">
<span class="play-button"></span>
<div class="pause-button"><span> </span><span> </span></div>
</div>
<div class="progress">
<div class="progress-bar">
<div class="button-holder">
<div class="progress-button"> </div>
</div>
</div>
<div class="time"><span class="ctime">0:00</span>
<span class="stime"> / </span>
<span class="ttime">0:26</span></div>
<div class="buffered"></div></div>
<div class="volume">
<div class="volume-holder">
<div class="volume-bar-holder">
<div class="volume-bar">
<div class="volume-button-holder">
<div class="volume-button"> </div>
</div>
</div>
</div>
</div>
<div class="volume-icon v-change-0">
<span> </span>
</div>
</div>
</div>
<div id="footer">
<p>VLC for tvOS is free, open-source software published by <a href="http://www.videolan.org" target="_blank">VideoLAN</a>. <br />Modification and redistribution is subject to both the <a href="http://opensource.org/licenses/GPL-2.0" target="_blank">GPLv2 (or later)</a> and the <a href="http://opensource.org/licenses/MPL-2.0" target="_blank">MPLv2</a> as well as further rights reserved by the VideoLAN non-profit organization.<br />You can find more information about licensing in the About dialog within the app.<br />VideoLAN, VLC and VLC media player are internationally registered trademarks of the <a href="http://www.videolan.org/videolan/" target="_blank">VideoLAN non-profit organization</a>.</p>
</div>
</div>
<div id="overlay" class="">
<div id="modal">
<ul class="downloads cf">
<!-- content of div.content goes here -->
</ul>
</div>
</div>
</body>
</html>
.player-control {
position: relative;
margin: auto;
width: 70%;
background: #2a2a2a;
box-sizing: border-box;
border-radius: 5px;
height: 80px;
-moz-box-sizing: border-box;
font-family: Arial, sans-serif;
padding: 0;
bottom: 20px;
z-index: 2;
opacity: 1;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
-webkit-transition: opacity 0.3s ease-in;
transition: opacity 0.3s ease-in;
-moz-user-select: none;
-webkit-user-select: none;
user-select: none;
cursor: default;
}
.player-control .progress {
width: 76%;
height: 8px;
border-radius: 5px;
background: #676767;
box-shadow: inset 0 -5px 10px rgba(0, 0, 0, 0.1);
float: left;
cursor: pointer;
margin: 40px 0 0 0;
padding: 0;
position: relative;
}
.player-control .progress-bar {
background: #fff;
box-shadow: inset -30px 0px 69px -20px #89f6f5;
border-radius: 5px;
height: 100%;
position: relative;
z-index: 999;
width: 0;
}
.player-control .button-holder {
position: relative;
left: 10px;
}
.player-control .progress-button {
top: -3px;
background: #fff;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.3);
border-radius: 30px;
width: 6px;
height: 15px;
position: absolute;
left: -1px;
text-decoration: overline;
}
.player-control [class^="buffered"] {
background: rgba(255, 255, 255, 0.1);
position: absolute;
top: 0;
left: 30px;
height: 100%;
border-radius: 5px;
z-index: 1;
}
.player-control .play-pause {
display: inline-block;
font-size: 2.3em;
float: left;
text-shadow: 0 0 0 #fff;
color: rgba(255, 255, 255, 0.8);
width: 10%;
padding: 32px 0 0 3%;
cursor: pointer;
font-variant: small-caps;
}
.player-control .play, .player-control .pause-button {
-webkit-transition: all 0.2s ease-out;
}
.player-control .play .pause-button, .player-control .pause .play-button {
display: none;
}
.player-control .pause-button {
padding: 0 2px;
box-sizing: border-box;
-moz-box-sizing: border-box;
height: 34px;
}
.player-control .pause-button span {
background: #fff;
width: 8px;
height: 24px;
float: left;
display: block;
}
.player-control .pause-button span:first-of-type {
margin: 0 4px 0 0;
}
.player-control .time {
color: #fff;
font-weight: bold;
font-size: 1.2em;
position: absolute;
right: 0;
top: 16px;
}
.player-control .stime, .ttime {
color: #444;
}
.player-control .play:hover {
text-shadow: 0 0 5px #fff;
}
.player-control .play:active, .pause-button:active span {
text-shadow: 0 0 7px #fff;
}
.player-control .pause-button:hover span {
box-shadow: 0 0 5px #fff;
}
.player-control .pause-button:active span {
box-shadow: 0 0 7px #fff;
}
.player-control .volume {
position: relative;
float: left;
width: 5%;
margin: 0 0 0 4%;
height: 100%;
}
.player-control .volume-icon {
padding: 1.5%;
height: 100%;
cursor: pointer;
box-sizing: border-box;
-moz-box-sizing: border-box;
-webkit-transition: all 0.15s linear;
}
.player-control .volume-holder {
height: 100px;
width: 100%;
background: black;
position: absolute;
display: none;
background: #4f4f4f;
left: 0;
border-radius: 5px 5px 0 0;
top: -100px;
}
.player-control .volume-bar-holder {
background: #333;
width: 8px;
box-shadow: inset 0px 0px 5px rgba(0, 0, 0, 0.3);
margin: 15px auto;
height: 80px;
border-radius: 5px;
position: relative;
cursor: pointer;
}
.player-control .volume-button {
background: #fff;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.3);
border-radius: 30px;
width: 15px;
height: 8px;
margin-left: -3px;
margin-top: -4px; /* height / 2 center the mouse */
}
.player-control .volume-button-holder {
position: relative;
top: 0;
}
.player-control .volume-bar {
background: #fff;
box-shadow: inset -30px 0px 69px -20px #89f6f5;
border-radius: 5px;
width: 100%;
height: 100%;
position: absolute;
bottom: 0;
}
.player-control .volume-icon span {
width: 20%;
height: 13%;
background-color: #fff;
display: block;
position: relative;
z-index: 1;
font-weight: bold;
top: 50%;
color: #fff;
left: 22%;
}
.player-control .volume-icon span:before,
.player-control .volume-icon span:after {
content: '';
position: absolute;
}
.player-control .volume-icon span:before {
width: 0;
height: 0px;
border: 1em solid transparent;
border-left: none;
border-right-color: #fff;
z-index: 2;
top: -6px;
left: 10%;
margin-top: -31%;
}
.player-control .volume-icon span:after {
width: 2%;
height: 2%;
border: 1px solid #fff;
left: 200%;
border-width: 0px 0px 0 0;
top: 5px;
border-radius: 0 50px 0 0;
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-ms-transform: rotate(45deg);
-o-transform: rotate(45deg);
transform: rotate(45deg);
font-variant: small-caps;
}
.player-control .v-change-10 span:after {
border-width: 10px 10px 0 0;
top: 0;
}
.player-control .v-change-9 span:after {
border-width: 9px 9px 0 0;
top: 1px;
}
.player-control .v-change-8 span:after {
border-width: 8px 8px 0 0;
top: 1px;
}
.player-control .v-change-7 span:after {
border-width: 7px 7px 0 0;
top: 2px;
}
.player-control .v-change-6 span:after {
border-width: 6px 6px 0 0;
top: 2px;
}
.player-control .v-change-5 span:after {
border-width: 5px 5px 0 0;
top: 3px;
}
.player-control .v-change-4 span:after {
border-width: 4px 4px 0 0;
top: 3px;
}
.player-control .v-change-3 span:after {
border-width: 3px 3px 0 0;
top: 4px;
}
.player-control .v-change-2 span:after {
border-width: 2px 2px 0 0;
top: 4px;
}
.player-control .v-change-1 span:after {
border-width: 1px 1px 0 0;
top: 5px;
}
.player-control .v-change-0 span:after {
border-width: 0px 0px 0 0;
top: 5px;
}
.player-control .v-change-0 span:after {
content: '+';
-webkit-transform: rotate(45deg);
font-size: 20px;
top: -7px;
left: 28px;
}
.player-control .title {
position: absolute;
line-height: 40px;
text-align: center;
width: 100%;
}
.player-control .volume:hover {
background-color: #4f4f4f;
}
.player-control .volume:hover .volume-holder {
display: block !important;
}
$(function() {
/**
* Ws is a wrapper of the WebSocket API
* @class Ws
*/
var Ws = function(options, managerOptions) {
options = options || {};
managerOptions = managerOptions || {};
if (!options.url)
throw "Cannot open a socket without a url";
//@TODO: add try catch & retry
this.onMessageCallbacks = [];
this.socket = new WebSocket(options.url);
this.url = options.url;
this.maxTry = 4;
this.recoTry = 0;
};
/**
* @method init
*/
Ws.prototype.init = function() {
var self = this;
this.socket.onopen = function() {
self._onOpen(this);
};
this.socket.onmessage = function(e) {
self.onMessage(e);
};
this.socket.onclose = function() {
self._onClose();
};
this.socket.onerror = function(error) {
self._onError(error);
};
};
/**
* @method _onOpen
* @param {Object} e
* @private
*/
Ws.prototype._onOpen = function(e) {
console.log(e);
this.recoTry = 0;
};
/**
* On message call message callbacks
* @method onMessage
* @param {Object} e
*/
Ws.prototype.onMessage = function(e) {
//@TODO: is json?
var message = $.parseJSON(e.data);
for (var i = 0, length = this.onMessageCallbacks.length; i < length; i++) {
var cb = this.onMessageCallbacks[i];
if (typeof cb === 'function') {
cb(message);
}
}
};
/**
* @method _onError
* @param {Object} e
* @private
*/
Ws.prototype._onError = function(e) {
console.log(e);
};
/**
* @method _onClose
* @param {Object} e
* @private
*/
Ws.prototype._onClose = function(e) {
console.log(e);
//try to reco?
if (this.maxTry > this.recoTry) {
return;
}
this.socket = new WebSocket(this.url);
this.init();
this.recoTry++;
};
/**
* Send stringify & send message to the socket
* @method sendMessage
* @param message
*/
Ws.prototype.sendMessage = function(message) {
message = JSON.stringify(message);
this.socket.send(message);
};
/**
* @class PlayerControl
*/
var PlayerControl = function(options) {
options = options || {};
if (!options.socket instanceof Ws)
throw "You need to provide a socket instance";
if (!options.element || !options.element.length)
throw "Element not found";
this._playing = false;
this._vClicked = false;
this._progDragged = false;
this._progClicked = false;
this.element = options.element;
this.socket = options.socket;
this.duration = 7200;
this.currentTime = 0;
this.volume = 0.5;
this._previousVolume = 1;
this.timeInterval = null;
this.enableInterval = false;
};
/**
* @method init
*/
PlayerControl.prototype.init = function() {
//Add event listener
var self = this;
//Update time
self.updateTime();
var buttonHeight = self.element.find('.volume-button').height();
var y = buttonHeight * this.volume * 10;
self.updateVolume(y);
var progWidth = this.element.find('.progress').width();
var x = (progWidth / self.duration) * self.currentTime;
self.updateProgress(x);
$(window).bind('mouseup', function(e) {
self._progClicked = false;
self._progDragged = false;
self._vClicked = false;
self._dragVolume = false;
});
// Shortcuts
$(window).bind('keydown', function(e) {
switch (e.which) {
case 32:
e.preventDefault();
self.playPause();
break;
case 37: // left
var time = self.currentTime - (self.duration / 100);
self.seekTo(true, time);
break;
case 39: // right
var time = self.currentTime + (self.duration / 100);
self.seekTo(true, time);
break;
case 38: // up
e.preventDefault();
var buttonHeight = self.element.find('.volume-button').height();
var y = buttonHeight * (self.volume + 0.1) * 10;
self.updateVolume(y);
break;
case 40: // down
e.preventDefault();
var buttonHeight = self.element.find('.volume-button').height();
var y = buttonHeight * (self.volume - 0.1) * 10;
self.updateVolume(y);
break;
default:
return; // exit this handler for other keys
}
});
this.element.find('.play-pause').bind('click', function() {
self.playPause();
});
this.element.find('.progress').bind('mousedown', function(e) {
self.mousedownProgress(e);
});
this.element.bind('mousemove', function(e) {
if (self._progClicked) {
self.dragProgress(e);
}
if (self._vClicked) {
self.dragVolume(e);
}
});
this.element.find('.volume-bar-holder').bind('mousedown', function(e) {
self._vClicked = true;
var y = self.element.find('.volume-bar-holder').height() - (e.pageY - self.element.find('.volume-bar-holder').offset().top);
self.updateVolume(y);
});
this.element.find('.volume-icon').bind('mousedown', function() {
if (self.volume) {
self._previousVolume = self.volume;
self.volume = 0;
} else {
self.volume = self._previousVolume;
}
var buttonHeight = self.element.find('.volume-button').height();
var y = buttonHeight * self.volume * 10;
self.updateVolume(y);
});
};
/**
* @method updateVolume
* @param {number} y
*/
PlayerControl.prototype.updateVolume = function(y) {
var buttonHeight = this.element.find('.volume-button').height();
var barHolderHeight = this.element.find('.volume-bar-holder').height();
if (y > barHolderHeight) {
y = barHolderHeight;
}
this.element.find('.volume-bar').css({
height: y + 'px'
});
//between 1 and 0
this.volume = this.element.find('.volume-bar').height() / barHolderHeight;
this.animateVolume();
};
/**
* @method playPause
*/
PlayerControl.prototype.playPause = function() {
if (this.isEnded()) {
return this.pause();
}
if (this._playing) {
this.pause(true);
}
else {
this.play(true);
}
};
/**
* @method animateVolume
*/
PlayerControl.prototype.animateVolume = function() {
var volumeIcon = this.element.find('.volume-icon');
volumeIcon.removeClass().addClass('volume-icon v-change-' + parseInt(this.volume * 10));
};
/**
* @method getMouseProgressPosition
* @param {Object} e - DOM event
* @returns {number}
*/
PlayerControl.prototype.getMouseProgressPosition = function(e) {
return e.pageX - this.element.find('.progress').offset().left;
};
/**
* @method mousedownProgress
* @param {Object} e - DOM event
*/
PlayerControl.prototype.mousedownProgress = function(e) {