Refactor WebSocketClient and AudioPlayer systems to use class.

parent fb658cce
Pipeline #900 passed with stage
in 1 minute and 13 seconds
......@@ -80,7 +80,7 @@
<div class="more">
<a href="" id="auth-signIn-display">Sign in</a>
</div>
</div>
</div>
</div>
<div class="sidenav-user" style="display: none;">
......@@ -106,7 +106,7 @@
</div>
</div>
<div class="container room" style="display: none;">
<div class="row">
<div class="col s12 m6 offset-m3 l4 push-l1 playlist_box">
......@@ -155,30 +155,30 @@
<a class="modal-close waves-effect waves-green btn btn-flat" id="playlist-create-submit">Créer</a>
</div>
</div>
<script src="js/lib/jquery-3.3.1.js" type="text/javascript"></script>
<script src="js/lib/materialize.js"></script>
<script src="js/lib/js.cookie.js" type="text/javascript"></script>
<script src="js/lib/utils.js" type="text/javascript"></script>
<script src="js/config.js" type="text/javascript"></script>
<script src="js/music.js" type="text/javascript"></script>
<script src="js/components/music.js" type="text/javascript"></script>
<script src="js/components/room.js" type="text/javascript"></script>
<script src="js/components/load.js" type="text/javascript"></script>
<script src="js/components/ws-ms.js" type="text/javascript"></script>
<script src="js/components/ws-ss.js" type="text/javascript"></script>
<script src="js/components/playlist.js" type="text/javascript"></script>
<script src="js/components/sidenav.js" type="text/javascript"></script>
<script src="js/components/sign.js" type="text/javascript"></script>
<script src="js/components/menu.js" type="text/javascript"></script>
<script src="js/components/search.js" type="text/javascript"></script>
<script src="js/components/breadcrumbs.js" type="text/javascript"></script>
<script src="js/Utils.js" type="text/javascript"></script>
<script src="js/WebSocketClient/Middleware.js" type="text/javascript"></script>
<script src="js/WebSocketClient/WebSocketClient.js" type="text/javascript"></script>
<script src="js/WebSocketClient/MusicWS.js" type="text/javascript"></script>
<script src="js/WebSocketClient/StreamWS.js" type="text/javascript"></script>
<script src="js/AudioPlayer/AudioPlayer.js" type="text/javascript"></script>
<script src="js/Init.js" type="text/javascript"></script>
</body>
</html>
\ No newline at end of file
const mimeCodec = 'audio/webm;codecs="opus"';
class AudioPlayer {
constructor(StreamWS) {
this.StreamWS = StreamWS;
this.blobs = [];
this.playerStarted = false;
this.appendPaused = true;
this.StreamWS.wsc.mw.onMessage.use((req, next) => {
this.blobs.push(req.data);
if (this.appendPaused && this.sourceBuffer)
this.appendNextBlob();
next();
});
this.init();
}
init() {
this.audio = document.querySelector('audio');
if ('MediaSource' in window && MediaSource.isTypeSupported(mimeCodec)) {
this.mediaSource = new MediaSource;
this.audio.src = URL.createObjectURL(this.mediaSource);
this.mediaSource.addEventListener('sourceopen', () => this.onSourceOpen());
} else {
console.error('Unsupported MIME type or codec: ', mimeCodec);
}
}
onSourceOpen() {
this.sourceBuffer = this.mediaSource.addSourceBuffer(mimeCodec);
this.sourceBuffer.addEventListener('updateend', () => this.appendNextBlob(), false);
this.appendNextBlob();
}
appendNextBlob() {
if (this.blobs.length) {
this.appendPaused = false;
this.sourceBuffer.appendBuffer(this.blobs.shift());
if (!this.playerStarted)
this.startPlayer();
} else {
this.appendPaused = true;
}
}
startPlayer() {
this.playerStarted = true;
const time = Date.now();
const delay = 1000 - (time % 1000);
setTimeout(() => {
console.log(`Player started at ${Date.now()}.`);
this.audio.play();
}, delay);
}
}
const MusicWS_ = new MusicWS();
const StreamWS_ = new StreamWS();
const AudioPlayer_ = new AudioPlayer(StreamWS_);
class Utils {
static String_UTF8Decode(str) {
str = str.replace(
/[\u00e0-\u00ef][\u0080-\u00bf][\u0080-\u00bf]/g,
function (c) {
const cc = ((c.charCodeAt(0) & 0x0f) << 12) | ((c.charCodeAt(1) & 0x3f) << 6) | (c.charCodeAt(2) & 0x3f);
return String.fromCharCode(cc);
}
);
str = str.replace(
/[\u00c0-\u00df][\u0080-\u00bf]/g,
function (c) {
const cc = (c.charCodeAt(0) & 0x1f) << 6 | c.charCodeAt(1) & 0x3f;
return String.fromCharCode(cc);
}
);
return str;
}
static String_toHHMMSS(str) {
let sec_num = parseInt(str, 10);
let hours = Math.floor(sec_num / 3600);
let minutes = Math.floor((sec_num - (hours * 3600)) / 60);
let seconds = sec_num - (hours * 3600) - (minutes * 60);
if (hours < 10)
hours = "0" + hours;
if (minutes < 10)
minutes = "0" + minutes;
if (seconds < 10)
seconds = "0" + seconds;
if (hours > 0)
return hours + ':' + minutes + ':' + seconds;
else
return minutes + ':' + seconds;
}
}
class Middleware {
constructor() {
if (!Array.prototype.last) {
Array.prototype.last = function last() {
return this[this.length - 1];
};
}
if (!Array.prototype.reduceOneRight) {
Array.prototype.reduceOneRight = function reduceOneRight() {
return this.slice(0, -1);
};
}
}
use(fn) {
this.go = (stack => (...args) => stack(...args.reduceOneRight(), () => {
const next = args.last();
fn.apply(this, [...args.reduceOneRight(),
next.bind.apply(next, [null, ...args.reduceOneRight()])]);
}))(this.go);
}
go(...args) {
const next = args.last();
next.apply(this, args.reduceOneRight());
}
}
const WS_PROTOCOL = $.urlParam('ws_protocol') ? $.urlParam('ws_protocol') : _WS_PROTOCOL;
const WS_HOST = $.urlParam('ws_host') ? $.urlParam('ws_host') : _WS_HOST;
const WS_URL = WS_PROTOCOL + "://" + WS_HOST;
class MusicWS {
constructor() {
this.wsc = new WebSocketClient('MWS', WS_URL);
this.wsc.mw.onOpen.use((req, next) => this.onOpen(req, next));
this.wsc.mw.onMessage.use((req, next) => {
if (!this.handleFeedPlaylist(req))
next();
});
this.wsc.mw.onMessage.use((req, next) => {
if (!this.handleRemoveVideo(req))
next();
});
this.wsc.mw.onMessage.use((req, next) => {
if (!this.handleStreamKey(req))
next();
});
}
onOpen(req, next) {
if (Cookies.get('token') !== undefined)
SignIn_Token_Request();
Room_init();
}
sendRequest(reqIn) {
this.wsc.sendRequest(reqIn);
}
sendData(data) {
this.wsc.sendData(data);
}
handleFeedPlaylist(req) {
if (!req.data.type || req.data.type !== 'feedPlaylist')
return false;
MusicPlaylist_feed(req.data.data);
return true;
}
handleRemoveVideo(req) {
if (!req.data.type || req.data.type !== 'removeVideo')
return false;
MusicPlaylist_RemoveDiv(req.data.data.video);
return true;
}
handleStreamKey(req) {
if (!req.data.type || req.data.type !== 'stream.key')
return false;
StreamWS_.setStreamKey(req.data.data);
return true;
}
}
const SWS_URL = WS_URL + "/stream";
let SWS_StreamKey;
class StreamWS {
constructor() {
this.wsc = new WebSocketClient('SWS', SWS_URL);
this.wsc.ws.binaryType = 'arraybuffer';
this.wsc.mw.onOpen.use((req, next) => this.onOpen(req, next));
this.streamKey = '';
}
onOpen(req, next) {
if (this.streamKey)
this.register();
next();
}
sendRequest(reqIn) {
this.wsc.sendRequest(reqIn);
}
sendData(data) {
this.wsc.sendData(data);
}
setStreamKey(streamKey) {
this.streamKey = streamKey;
if (this.wsc.ws.readyState === WebSocket.OPEN)
this.register();
}
register() {
let notif = {
type: 'register',
data: this.streamKey
};
this.wsc.sendData(notif);
}
}
class WebSocketClient {
constructor(name, url) {
this.name = name;
this.url = url;
this.mw = {};
this.mw.onOpen = new Middleware();
this.mw.onMessage = new Middleware();
this.mw.onClose = new Middleware();
this.mw.onError = new Middleware();
this.ws = new WebSocket(this.url);
this.ws.onopen = (event) => {
const req = {
client: this
};
this.mw.onOpen.go(req, () => {
});
};
this.ws.onmessage = (event) => {
const req = {
client: this,
};
if (event.data instanceof ArrayBuffer)
req.data = event.data;
else
req.data = JSON.parse(Utils.String_UTF8Decode(event.data));
this.mw.onMessage.go(req, () => {
});
};
this.ws.onclose = (event) => {
const req = {
client: this
};
this.mw.onClose.go(req, () => {
});
};
this.ws.onerror = (event) => {
const req = {
client: this,
data: event
};
this.mw.onError.go(req, () => {
});
};
this.initLogger();
this.requestId = 1;
this.requestCallback = new Map();
this.mw.onMessage.use((req, next) => {
if (!this.handleRequest(req))
next();
});
}
initLogger() {
this.mw.onOpen.use((req, next) => {
console.log(`[${this.name}] Connected to ${this.url}.`);
next();
});
this.mw.onMessage.use((req, next) => {
console.log(`[${this.name}] Message :`);
console.log(req.data);
next();
});
this.mw.onClose.use((req, next) => {
console.log(`[${this.name}] Disconnected.`);
next();
});
this.mw.onError.use((req, next) => {
console.error(`[${this.name}] Error : `, req.data);
next();
});
}
sendRequest(reqIn) {
if (this.ws.readyState !== WebSocket.OPEN) {
return;
}
if (!reqIn || typeof reqIn !== 'object')
return;
if (typeof reqIn.type === 'undefined')
return;
let reqOut = {
type: reqIn.type
};
if (typeof reqIn.data !== 'undefined')
reqOut.data = reqIn.data;
if (typeof reqIn.success === 'function')
reqOut.id = this.requestId++;
console.log(`[${this.name}] Request :`);
console.log(reqOut);
if (typeof reqIn.success === 'function') {
let callback = {};
callback.success = reqIn.success;
if (typeof reqIn.error === 'function')
callback.error = reqIn.error;
this.requestCallback.set(reqOut.id, callback);
}
this.ws.send(JSON.stringify(reqOut));
}
sendData(data) {
if (this.ws.readyState !== WebSocket.OPEN) {
return;
}
console.log(`[${this.name}] Send :`);
console.log(data);
this.ws.send(JSON.stringify(data));
}
handleRequest(req) {
if (typeof req.data.type === 'undefined' && typeof req.data.id !== 'undefined') {
let callback = this.requestCallback.get(req.data.id);
if (!callback)
return false;
if (typeof req.data.result !== 'undefined') {
callback.success(req.data.result);
} else {
if (typeof callback.error === 'function')
callback.error(req.data.error);
}
return true;
}
return false;
}
}
......@@ -9,8 +9,8 @@ $('.menu-left .logout').on('click', function(e) {
$('.breadcrumb[page=roomlist]').on('click', function(e) {
e.preventDefault();
MWS_request({
MusicWS_.sendRequest({
type: 'room.quit',
success: function(data) {
console.log(data)
......
......@@ -11,7 +11,7 @@ function MusicPlaylist_CreateDiv(data) {
let music = data.music;
itemHTML += '<img class="circle" src="' + music.thumbnails.url + '"/>';
itemHTML += '<span class="title">' + music.title + '</span>';
itemHTML += '<p>(' + String_toHHMMSS(music.duration) + ')<br />';
itemHTML += '<p>(' + Utils.String_toHHMMSS(music.duration) + ')<br />';
if(data.sender !== undefined) itemHTML += 'Proposé par <span class="sender">' + data.sender + '</span>';
itemHTML += '</p>';
itemHTML += '<div class="tools secondary-content">';
......
......@@ -24,7 +24,7 @@ $('.container.playlist-own .playlist-container').on('click', '.card-delete a', f
});
function Playlist_GetOwn_Request(callback) {
MWS_request({
MusicWS_.sendRequest({
type: 'playlist.listOwn',
success: callback,
error: log
......@@ -97,7 +97,7 @@ function Playlist_GetOwn_Display(data) {
};
function Playlist_Create_Request() {
MWS_request({
MusicWS_.sendRequest({
type: 'playlist.create',
data: {
name: $('#playlist-create-name').val()
......@@ -114,7 +114,7 @@ function Playlist_Create_Success(data) {
}
function Playlist_Destroy_Request(playlist_id) {
MWS_request({
MusicWS_.sendRequest({
type: 'playlist.delete',
data: {
id: playlist_id
......@@ -130,7 +130,7 @@ function Playlist_Destroy_Success(playlist_id) {
}
function Playlist_GetOne_Request(id, callback) {
MWS_request({
MusicWS_.sendRequest({
type: 'playlist.get',
data: {
id: id
......@@ -166,7 +166,7 @@ function Playlist_GetOne_Fill(data) {
}
function Playlist_AddMusic_Request(playlist_id, music) {
MWS_request({
MusicWS_.sendRequest({
type: 'playlist.addMusic',
data: {
playlist: playlist_id,
......
......@@ -6,7 +6,7 @@ $('.container.room-list .rooms-container').on('click', '.card a.join', function(
});
function Room_GetList_Request(callback) {
MWS_request({
MusicWS_.sendRequest({
type: 'room.list',
success: callback,
error: log
......@@ -20,7 +20,7 @@ function Room_GetList_Display(data) {
Search_Hide();
$('.container').hide();
$('.container.room-list .rooms-container').html('');
$.each(data, function(index, room) {
let card = $('<div>');
......@@ -76,7 +76,7 @@ function Room_init() {
}
function Room_RemoveVideo(id) {
MWS_request({
MusicWS_.sendRequest({
type: 'TODO.room.removeMedia',
data: {
video: id
......@@ -85,7 +85,7 @@ function Room_RemoveVideo(id) {
}
function Room_AddMusic_Request(room_id, music) {
MWS_request({
MusicWS_.sendRequest({
type: 'room.addMusic',
data: {
room: room_id,
......@@ -101,7 +101,7 @@ function Room_AddMusic_Success(data) {
}
function Room_Join_Request(room_id, callback) {
MWS_request({
MusicWS_.sendRequest({
type: 'room.join',
data: {
room: room_id
......@@ -119,4 +119,4 @@ function Room_One_Display(data) {
$('.container').hide();
$('.container.room').show();
}
\ No newline at end of file
}
......@@ -38,7 +38,7 @@ function Search_onAutocomplete(data) {
}
function Search_sendSearchQuery(q) {
MWS_request({
MusicWS_.sendRequest({
type: "search",
data: {
q: q
......
......@@ -11,7 +11,7 @@ $("#auth-signOut").on('click', function(e) {
})
function SignIn_Local_Request() {
MWS_request({
MusicWS_.sendRequest({
type: 'auth.signIn',
data: {
method: 'local',
......@@ -29,7 +29,7 @@ function SignIn_Local_Request() {
}
function SignIn_Token_Request() {
MWS_request({
MusicWS_.sendRequest({
type: 'auth.signIn',
data: {
method: 'token',
......@@ -43,8 +43,6 @@ function SignIn_Token_Request() {
}
function SignIn_Success(data) {
console.log(data);
if (data.token)
Cookies.set('token', data.token);
......@@ -57,7 +55,7 @@ function SignOut_Request() {
if (Cookies.set('token'))
Cookies.remove('token');
MWS_request({
MusicWS_.sendRequest({
type: 'auth.signOut',
success: SignOut_Success,
error: log
......
let WS_PROTOCOL = $.urlParam('ws_protocol') ? $.urlParam('ws_protocol') : _WS_PROTOCOL;
let WS_HOST = $.urlParam('ws_host') ? $.urlParam('ws_host') : _WS_HOST;
let WS_URL = WS_PROTOCOL + "://" + WS_HOST;
let mws;
let MMWS_requestSuccessCallback = [];
let MMWS_requestErrorCallback = [];
let MMWS_request_id = 0;
function MWS_init() {
mws = new WebSocket(WS_URL);
mws.onopen = function(evt) { MWS_onOpen(evt); };
mws.onclose = function(evt) { MWS_onClose(evt); };
mws.onmessage = function(evt) { MWS_onMessage(evt); };
mws.onerror = function(evt) { MWS_onError(evt); };
}
function MWS_onOpen(evt) {
console.log(`[MWS] Connected to ${WS_URL}.`);
if(Cookies.get('token') !== undefined)
SignIn_Token_Request();
Room_init();
}
function MWS_onClose(evt) {
console.log(`[MWS] Disconnected.`);
M.toast(`Vous avez été déconnecté du serveur !`, 5000, 'rounded red');
}
function MWS_onMessage(evt) {
console.log(`[MWS] Message :`);
let res = JSON.parse(String_UTF8Decode(evt.data));
console.log(res);
if (typeof res.type === 'undefined') {
if (typeof res.id !== 'undefined') {
if (typeof res.result !== 'undefined') {
if(MMWS_requestSuccessCallback[res.id] && typeof MMWS_requestSuccessCallback[res.id] === 'function')
MMWS_requestSuccessCallback[res.id](res.result);
} else {
if(MMWS_requestErrorCallback[res.id] && typeof MMWS_requestErrorCallback[res.id] === 'function')
MMWS_requestErrorCallback[res.id](res.error);
}
}
} else {
let data = res.data;
switch(res.type) {
case "feedPlaylist":
MusicPlaylist_feed(data);
break;
case "removeVideo":
MusicPlaylist_RemoveDiv(data.video);
break;
case "stream.key":
SWS_StreamKey = data;
if(typeof SWS !== 'undefined' && SWS.readyState === SWS.OPEN) {
StreamRegister();
}
break;
}
}
}
function MWS_onError(evt) {
console.log(`[MWS] Error : ${evt.data}`);
}
function MWS_isReady() {
return mws.readyState == 1;
}
function MWS_request(obj) {
if (!MWS_isReady()) {
Materialize.toast(`Vous n'êtes pas connecté au serveur !`, 2000, 'rounded red');
return ;
}
if (!obj || typeof obj !== 'object')
return ;
if (typeof obj.type === 'undefined')
return ;
let req = {
type: obj.type
};
if(typeof obj.data !== 'undefined')
req.data = obj.data;
if(typeof obj.success === 'function')
req.id = ++MMWS_request_id;
console.log(`[MWS] Request :`);
console.log(req);
mws.send(JSON.stringify(req));