Skip to content

Commit c51e0b5

Browse files
committed
improved media-player widget + added template
1 parent 3095a54 commit c51e0b5

File tree

4 files changed

+365
-49
lines changed

4 files changed

+365
-49
lines changed

www/scripts/sepiaFW.client.controls.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,7 @@ function sepiaFW_build_client_controls(sepiaSessionId){
333333
console.log('Service: ' + controlData.service);
334334
console.log('URI: ' + controlData.uri);
335335
*/
336+
SepiaFW.debug.info("Client controls - SearchForMusic service: " + controlData.service);
336337

337338
//Stop other players - TODO: move down?
338339
Controls.media({

www/scripts/sepiaFW.ui.cards.embed.js

Lines changed: 81 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,29 @@
22
function sepiaFW_build_ui_cards_embed(){
33
var Embed = {};
44

5-
//TODO: add MediaPlayer
6-
/*
7-
- Controls: start, pause/stop, resume, next, previous
8-
- Implement async. callback with msgId and timeout
5+
//MediaPlayer
6+
/* TODO:
7+
- Test and finish controls: start, pause/stop, resume, next, previous, vol...
8+
- Implement async. callback with msgId and timeout?
99
- Get media info and use SepiaFW.audio.setPlayerTitle('', '') + setMediaSessionState (SepiaFW.audio)
1010
*/
1111
var playerWidgets = {
1212
default: "<assist_server>/widgets/mp-default.html",
1313
embedded: "<assist_server>/widgets/mp-default.html",
1414
spotify: "<assist_server>/widgets/mp-spotify.html",
1515
apple_music: "<assist_server>/widgets/mp-apple-music.html",
16-
youtube: "<assist_server>/widgets/mp-youtube.html"
16+
youtube: "<assist_server>/widgets/mp-youtube.html",
17+
soundcloud: "<assist_server>/widgets/mp-soundcloud.html"
1718
}
1819

1920
var activeMediaPlayers = {};
2021
var lastActiveMediaPlayer = undefined;
22+
var maxActiveMediaPlayers = 5; //NOTE: "active" does not mean "playing" but "exists" with event listeners
2123

2224
function getNewMediaPlayerId(){
2325
mediaPlayerLastId++;
2426
var overflow = false;
25-
if (mediaPlayerLastId > 5){
27+
if (mediaPlayerLastId > maxActiveMediaPlayers){
2628
mediaPlayerLastId = 1;
2729
overflow = true;
2830
}
@@ -31,7 +33,7 @@ function sepiaFW_build_ui_cards_embed(){
3133
//remove old players
3234
var oldMp = activeMediaPlayers[newId];
3335
if (oldMp && oldMp.exists()){
34-
oldMp.release();
36+
oldMp.cardItem.sepiaCardOnBeforeRemove();
3537
$(oldMp.cardItem).remove();
3638
}
3739
//console.error("RELEASED and REMOVED", newId, activeMediaPlayers[newId]); //DEBUG
@@ -69,6 +71,7 @@ function sepiaFW_build_ui_cards_embed(){
6971
allowIframe = 'encrypted-media *;';
7072
}
7173
var iframe = document.createElement("iframe");
74+
iframe.className = "cardItemBlock onlyBlock";
7275
iframe.src = contentUrl;
7376
iframe.width = "100%";
7477
iframe.height = 50; //can be set via postMessage interface
@@ -79,7 +82,7 @@ function sepiaFW_build_ui_cards_embed(){
7982
mediaPlayerDiv.appendChild(iframe);
8083
//loading overlay
8184
var loadOverlay = document.createElement('div');
82-
loadOverlay.className = "cardItemOverlay";
85+
loadOverlay.className = "cardItemOverlay cardItemBlock";
8386
loadOverlay.innerHTML = "<p>Loading</p>";
8487
mediaPlayerDiv.appendChild(loadOverlay);
8588
return {
@@ -152,7 +155,7 @@ function sepiaFW_build_ui_cards_embed(){
152155
//options
153156
//required: parentElement, widget or widgetUrl
154157
//optional: brand
155-
//events: onready
158+
//events: onready (NOTE: will trigger only once)
156159

157160
var thisPlayer = this;
158161
//console.error("Embedded MediaPlayer", options); //DEBUG
@@ -172,17 +175,27 @@ function sepiaFW_build_ui_cards_embed(){
172175
//Widget URL
173176
var widgetUrl = (options.widgetUrl || playerWidgets[options.widget]).trim();
174177
widgetUrl = SepiaFW.config.replacePathTagWithActualPath(widgetUrl);
175-
var widgetIsSameOrigin = SepiaFW.tools.isSameOrigin(widgetUrl);
176-
var widgetIsSepiaFileHost = SepiaFW.config.urlIsSepiaFileHost(widgetUrl);
177-
var widgetIsRemote = (widgetUrl.indexOf("http:") == 0) || (widgetUrl.indexOf("https:") == 0) || (widgetUrl.indexOf("ftp:") == 0);
178-
var widgetIsTrusted = widgetIsSameOrigin || widgetIsSepiaFileHost || !widgetIsRemote;
178+
//check URL - NOTE: currently we do not allow unknown URLs
179+
var isValidLocalURL = SepiaFW.tools.isRelativeFileUrl(widgetUrl, "html");
180+
var isTrustedRemoteUrl = SepiaFW.tools.isRemoteFileUrl(widgetUrl, "html")
181+
&& (SepiaFW.tools.isSameOrigin(widgetUrl) || SepiaFW.config.urlIsSepiaFileHost(widgetUrl));
182+
var widgetIsTrusted = isValidLocalURL || isTrustedRemoteUrl;
183+
if (!widgetIsTrusted){
184+
SepiaFW.debug.error("WARNING: Widget URL has remote location and was BLOCKED due to security restrictions! - URL: " + widgetUrl);
185+
SepiaFW.ui.showSafeWarningPopup("Warning", [
186+
"SEPIA was asked to open a widget with an untrusted remote URL. The request has been blocked due to security restrictions.",
187+
"If you want to use this widget please ask an admin to move it to a secure location (e.g. the SEPIA file server).",
188+
"URL:"
189+
], widgetUrl);
190+
return;
191+
}
179192

180193
//URL parameters (so we have the data during loading)
181194
widgetUrl = SepiaFW.tools.setParameterInURL(widgetUrl, "skinStyle", SepiaFW.ui.getSkinStyle());
182195
widgetUrl = SepiaFW.tools.setParameterInURL(widgetUrl, "skinId", SepiaFW.ui.getSkin());
183-
//add more: language, isMobile, env, ...?
184-
185-
console.error("Media Player URL", widgetUrl, "isTrusted", widgetIsTrusted); //DEBUG
196+
widgetUrl = SepiaFW.tools.setParameterInURL(widgetUrl, "lang", SepiaFW.config.appLanguage);
197+
widgetUrl = SepiaFW.tools.setParameterInURL(widgetUrl, "mobile", SepiaFW.ui.isMobile);
198+
SepiaFW.debug.info("Embedded MediaPlayer - Loading widget URL: " + widgetUrl);
186199

187200
//Create card (DOM element)
188201
var mpObj = createMediaPlayerDomElement(playerId, widgetUrl, widgetIsTrusted, function(){
@@ -191,23 +204,25 @@ function sepiaFW_build_ui_cards_embed(){
191204
});
192205
thisPlayer.iframe = mpObj.iframe;
193206
thisPlayer.cardItem = mpObj.card;
207+
thisPlayer.overlay = mpObj.overlay;
194208
thisPlayer.exists = function(){
195209
var ex = thisPlayer.cardItem && document.body.contains(thisPlayer.cardItem);
196-
if (!ex && state != 11){
197-
thisPlayer.release();
198-
}
210+
/*if (!ex && state != 11){
211+
thisPlayer.release(); //this should be used but it can lead to null pointer in sync. follow-up checks :-/
212+
}*/
199213
return ex;
200214
}
201215
options.parentElement.appendChild(mpObj.card);
202216

203217
//Card move and delete actions
204-
mpObj.card.sepiaCardOnBeforeMove = function(){
205-
console.error("MOVE EVENT", playerId); //DEBUG
206-
//TODO: rescue state - anything else? (note: onready is a one-time event now)
207-
}
208-
mpObj.card.sepiaCardOnBeforeRemove = function(){
209-
//TODO: currently called nowhere
210-
console.error("REMOVE EVENT", playerId); //DEBUG
218+
thisPlayer.cardItem.sepiaCardOnBeforeMove = function(){
219+
//TODO: no reliable way to call it atm (can we use MutationObserver? But on what parent?)
220+
//console.error("BEFORE MOVE EVENT", playerId); //DEBUG
221+
//anything? (note: onready is a one-time event now, settings will restore automatically)
222+
}
223+
thisPlayer.cardItem.sepiaCardOnBeforeRemove = function(){
224+
//TODO: no reliable way to call it atm (too many events: history clear, parent list remove, etc...)
225+
//console.error("BEFORE REMOVE EVENT", playerId); //DEBUG
211226
thisPlayer.release();
212227
}
213228

@@ -238,8 +253,14 @@ function sepiaFW_build_ui_cards_embed(){
238253
if (ev.state != undefined){
239254
stateHandler(ev);
240255
}
256+
if (ev.settings){
257+
widgetSettings = ev.settings;
258+
}
241259
}
242260

261+
//Settings backup
262+
var widgetSettings; //settings specific to widget that will be submitted e.g. after move or reload
263+
243264
//States
244265
var state = 0; //0: created, 1: ready, 2: playing, 3: paused, 10: error, 11: closed
245266
var isOnHold = false; //will start to play after next idle event (usually)
@@ -248,34 +269,42 @@ function sepiaFW_build_ui_cards_embed(){
248269

249270
function stateHandler(ev){
250271
var data = ev.data || {};
272+
273+
//on-ready
251274
if (ev.state == 1){
252-
//on-ready
253275
state = ev.state;
254276
if (data.size && data.size.height){
255277
thisPlayer.iframe.style.height = data.size.height;
256278
}
257-
setTimeout(function(){ $(mpObj.overlay).hide(); }, 500);
279+
setTimeout(function(){ $(thisPlayer.overlay).hide(); }, 500);
280+
//settings stored?
281+
if (widgetSettings){
282+
thisPlayer.restoreSettings(widgetSettings);
283+
}
258284
//callback (only once)
259285
if (options.onready){
260286
options.onready();
261287
options.onready = undefined;
262288
}
263289

290+
//on-play
264291
}else if (ev.state == 2){
265-
//on-play
266292
state = ev.state;
267293
SepiaFW.audio.broadcastAudioEvent("embedded-media-player", "start");
268-
lastActiveMediaPlayer = thisPlayer; //TODO: use?
294+
lastActiveMediaPlayer = thisPlayer;
269295
SepiaFW.debug.info("Embedded MediaPlayer - Last active player switched to: " + playerId);
270296
//Embed.stopAllMediaPlayers(thisPlayer) //this should be handled globally for ALL media
297+
if (ev.data && ev.data.meta){
298+
console.error("META", ev.data.meta); //DEBUG
299+
}
271300

301+
//on-pause
272302
}else if (ev.state == 3){
273-
//on-pause
274303
state = ev.state;
275304
SepiaFW.audio.broadcastAudioEvent("embedded-media-player", "pause");
276305

306+
//on-error
277307
}else if (ev.state == 10){
278-
//on-error
279308
var lastWasError = (state == 10);
280309
state = ev.state;
281310
//ev.code-ev.name: 1-UnknownEvent, 2-NoMediaMatch, 3-PlayerErrorNotAllowed, 4-PlayerError (any), ... tbd
@@ -292,6 +321,8 @@ function sepiaFW_build_ui_cards_embed(){
292321
}
293322
SepiaFW.debug.error("Embedded MediaPlayer - Error:", ev.name, ev.message, ev.code);
294323
SepiaFW.audio.broadcastAudioEvent("embedded-media-player", "error");
324+
325+
//other
295326
}else{
296327
SepiaFW.debug.error("Embedded MediaPlayer - Unknown state: " + ev.state);
297328
return;
@@ -326,14 +357,15 @@ function sepiaFW_build_ui_cards_embed(){
326357
//include 'isOnHold'? (atm we don't check this for internal player)
327358
}
328359

329-
//Controls - TODO: implement callbacks?! more?
360+
//Controls
330361
thisPlayer.play = function(doneCallback, errorCallback){
331362
sendEvent({controls: "play"});
332363
isWaitingForPlay = true;
333364
}
334365
thisPlayer.pause = function(doneCallback, errorCallback){
335366
sendEvent({controls: "pause"});
336367
isWaitingForPause = true;
368+
isWaitingForPlay = false;
337369
isOnHold = false;
338370
}
339371
thisPlayer.fadeOut = function(doneCallback, errorCallback){
@@ -351,6 +383,7 @@ function sepiaFW_build_ui_cards_embed(){
351383
thisPlayer.next = function(doneCallback, errorCallback){
352384
sendEvent({controls: "next"});
353385
}
386+
//TODO: implement callbacks?! more?
354387
thisPlayer.previous = function(doneCallback, errorCallback){
355388
sendEvent({controls: "previous"});
356389
}
@@ -360,6 +393,7 @@ function sepiaFW_build_ui_cards_embed(){
360393
thisPlayer.volumeDown = function(doneCallback, errorCallback){
361394
sendEvent({controls: "volumeDown"});
362395
}
396+
363397
//Content
364398
thisPlayer.mediaRequest = function(type, request, autoplay, safeRequest, doneCallback, errorCallback){
365399
SepiaFW.audio.broadcastAudioEvent("embedded-media-player", "prepare");
@@ -381,10 +415,22 @@ function sepiaFW_build_ui_cards_embed(){
381415
safeRequest: safeRequest //came from assistant or private channel?
382416
});
383417
}
418+
//Settings
419+
thisPlayer.restoreSettings = function(data){
420+
sendEvent({
421+
settings: data
422+
});
423+
}
424+
425+
//Specials
426+
thisPlayer.openInExternalPage = function(){
427+
//TODO: implement
428+
SepiaFW.ui.showPopup(SepiaFW.local.g("cant_execute") + " (Coming Soon)");
429+
}
384430

385-
//stop, release resources and remove handle
431+
//Release resources and remove handler
386432
thisPlayer.release = function(doneCallback, errorCallback){
387-
// - remove from active players, block incoming events
433+
//remove from active players, block incoming events
388434
state = 11;
389435
if (lastActiveMediaPlayer == thisPlayer) lastActiveMediaPlayer = undefined;
390436
delete activeMediaPlayers[playerId];

0 commit comments

Comments
 (0)