@stremio/stremio-video 0.0.22 → 0.0.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -2,6 +2,7 @@ var ChromecastSenderVideo = require('../ChromecastSenderVideo');
|
|
|
2
2
|
var ShellVideo = require('../ShellVideo');
|
|
3
3
|
var HTMLVideo = require('../HTMLVideo');
|
|
4
4
|
var TizenVideo = require('../TizenVideo');
|
|
5
|
+
var WebOsVideo = require('../WebOsVideo');
|
|
5
6
|
var IFrameVideo = require('../IFrameVideo');
|
|
6
7
|
var YouTubeVideo = require('../YouTubeVideo');
|
|
7
8
|
var withStreamingServer = require('../withStreamingServer');
|
|
@@ -32,10 +33,16 @@ function selectVideoImplementation(commandArgs, options) {
|
|
|
32
33
|
if (typeof global.tizen !== 'undefined') {
|
|
33
34
|
return withStreamingServer(withHTMLSubtitles(TizenVideo));
|
|
34
35
|
}
|
|
36
|
+
if (typeof global.webOS !== 'undefined') {
|
|
37
|
+
return withStreamingServer(withHTMLSubtitles(WebOsVideo));
|
|
38
|
+
}
|
|
35
39
|
return withStreamingServer(withHTMLSubtitles(HTMLVideo));
|
|
36
40
|
}
|
|
37
41
|
|
|
38
42
|
if (typeof commandArgs.stream.url === 'string') {
|
|
43
|
+
if (typeof global.webOS !== 'undefined') {
|
|
44
|
+
return withHTMLSubtitles(WebOsVideo);
|
|
45
|
+
}
|
|
39
46
|
if (typeof global.tizen !== 'undefined') {
|
|
40
47
|
return withHTMLSubtitles(TizenVideo);
|
|
41
48
|
}
|
|
@@ -0,0 +1,1089 @@
|
|
|
1
|
+
var EventEmitter = require('eventemitter3');
|
|
2
|
+
var cloneDeep = require('lodash.clonedeep');
|
|
3
|
+
var deepFreeze = require('deep-freeze');
|
|
4
|
+
var ERROR = require('../error');
|
|
5
|
+
|
|
6
|
+
function luna(params, call, fail, method) {
|
|
7
|
+
if (call) params.onSuccess = call || function() {};
|
|
8
|
+
|
|
9
|
+
params.onFailure = function () { // function(result)
|
|
10
|
+
// console.log('WebOS',(params.method || method) + ' [fail][' + result.errorCode + '] ' + result.errorText );
|
|
11
|
+
|
|
12
|
+
if (fail) fail();
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
window.webOS.service.request(method || 'luna://com.webos.media', params);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function runWebOS(params, failed) {
|
|
19
|
+
// console.log('run web os', params);
|
|
20
|
+
window.webOS.service.request('luna://com.webos.applicationManager', {
|
|
21
|
+
method: 'launch',
|
|
22
|
+
parameters: {
|
|
23
|
+
'id': params.need,
|
|
24
|
+
'params': {
|
|
25
|
+
'payload':[
|
|
26
|
+
{
|
|
27
|
+
'fullPath': params.url,
|
|
28
|
+
'artist':'',
|
|
29
|
+
'subtitle':'',
|
|
30
|
+
'dlnaInfo':{
|
|
31
|
+
'flagVal':4096,
|
|
32
|
+
'cleartextSize':'-1',
|
|
33
|
+
'contentLength':'-1',
|
|
34
|
+
'opVal':1,
|
|
35
|
+
'protocolInfo':'http-get:*:video/x-matroska:DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=01700000000000000000000000000000',
|
|
36
|
+
'duration':0
|
|
37
|
+
},
|
|
38
|
+
'mediaType':'VIDEO',
|
|
39
|
+
'thumbnail':'',
|
|
40
|
+
'deviceType':'DMR',
|
|
41
|
+
'album':'',
|
|
42
|
+
'fileName': params.name,
|
|
43
|
+
'lastPlayPosition': params.position
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
onSuccess: function () {
|
|
49
|
+
// console.log('The app is launched');
|
|
50
|
+
},
|
|
51
|
+
onFailure: function () { // function(inError)
|
|
52
|
+
// console.log('Player', 'Failed to launch the app ('+params.need+'): ', '[' + inError.errorCode + ']: ' + inError.errorText);
|
|
53
|
+
|
|
54
|
+
if (params.need === 'com.webos.app.photovideo') {
|
|
55
|
+
params.need = 'com.webos.app.smartshare';
|
|
56
|
+
runWebOS(params);
|
|
57
|
+
} else if(params.need === 'com.webos.app.smartshare') {
|
|
58
|
+
params.need = 'com.webos.app.mediadiscovery';
|
|
59
|
+
runWebOS(params);
|
|
60
|
+
} else if (params.need === 'com.webos.app.mediadiscovery') {
|
|
61
|
+
failed();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
var webOsColors = ['black', 'white', 'yellow', 'red', 'green', 'blue'];
|
|
68
|
+
var stremioColors = {
|
|
69
|
+
// rgba
|
|
70
|
+
'rgba(0, 0, 0, 255)': 'black',
|
|
71
|
+
'rgba(255, 255, 255, 255)': 'white',
|
|
72
|
+
'rgba(255, 255, 0, 255)': 'yellow',
|
|
73
|
+
'rgba(255, 0, 0, 255)': 'red',
|
|
74
|
+
'rgba(0, 255, 0, 255)': 'green',
|
|
75
|
+
'rgba(0, 0, 255, 255)': 'blue',
|
|
76
|
+
// rgba case 2
|
|
77
|
+
'rgba(0, 0, 0, 1)': 'black',
|
|
78
|
+
'rgba(255, 255, 255, 1)': 'white',
|
|
79
|
+
'rgba(255, 255, 0, 1)': 'yellow',
|
|
80
|
+
'rgba(255, 0, 0, 1)': 'red',
|
|
81
|
+
'rgba(0, 255, 0, 1)': 'green',
|
|
82
|
+
'rgba(0, 0, 255, 1)': 'blue',
|
|
83
|
+
// rgb
|
|
84
|
+
'rgba(0, 0, 0)': 'black',
|
|
85
|
+
'rgba(255, 255, 255)': 'white',
|
|
86
|
+
'rgba(255, 255, 0)': 'yellow',
|
|
87
|
+
'rgba(255, 0, 0)': 'red',
|
|
88
|
+
'rgba(0, 255, 0)': 'green',
|
|
89
|
+
'rgba(0, 0, 255)': 'blue',
|
|
90
|
+
// 8-digit hex
|
|
91
|
+
'#000000FF': 'black',
|
|
92
|
+
'#FFFFFFFF': 'white',
|
|
93
|
+
'#FFFF00FF': 'yellow',
|
|
94
|
+
'#FF0000FF': 'red',
|
|
95
|
+
'#00FF00FF': 'green',
|
|
96
|
+
'#0000FFFF': 'blue',
|
|
97
|
+
// 6-digit hex
|
|
98
|
+
'#000000': 'black',
|
|
99
|
+
'#FFFFFF': 'white',
|
|
100
|
+
'#FFFF00': 'yellow',
|
|
101
|
+
'#FF0000': 'red',
|
|
102
|
+
'#00FF00': 'green',
|
|
103
|
+
'#0000FF': 'blue'
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
function stremioSubOffsets(offset) {
|
|
107
|
+
if (offset === 0) {
|
|
108
|
+
return -3;
|
|
109
|
+
} else if (offset <= 2) {
|
|
110
|
+
return -2;
|
|
111
|
+
} else if (offset <= 3) {
|
|
112
|
+
return -1;
|
|
113
|
+
} else if (offset <= 5) {
|
|
114
|
+
return 0;
|
|
115
|
+
} else if (offset <= 10) {
|
|
116
|
+
return 1;
|
|
117
|
+
} else if (offset <= 25) {
|
|
118
|
+
return 2;
|
|
119
|
+
} else if (offset <= 50) {
|
|
120
|
+
return 3;
|
|
121
|
+
} else if (offset <= 100) {
|
|
122
|
+
return 4;
|
|
123
|
+
}
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function stremioSubSizes(size) {
|
|
128
|
+
// there is also: 0 (tiny)
|
|
129
|
+
// adding zero will break the logic
|
|
130
|
+
if (size <= 75) {
|
|
131
|
+
return 1;
|
|
132
|
+
} else if (size <= 100) {
|
|
133
|
+
return 2;
|
|
134
|
+
} else if (size <= 150) {
|
|
135
|
+
return 3;
|
|
136
|
+
} else if (size <= 250) {
|
|
137
|
+
return 4;
|
|
138
|
+
}
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function WebOsVideo(options) {
|
|
143
|
+
|
|
144
|
+
options = options || {};
|
|
145
|
+
|
|
146
|
+
var containerElement = options.containerElement;
|
|
147
|
+
if (!(containerElement instanceof HTMLElement)) {
|
|
148
|
+
throw new Error('Container element required to be instance of HTMLElement');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
var knownMediaId = false;
|
|
152
|
+
|
|
153
|
+
var subSize = 75;
|
|
154
|
+
|
|
155
|
+
var disabledSubs = true;
|
|
156
|
+
|
|
157
|
+
var subscribed = false;
|
|
158
|
+
|
|
159
|
+
var currentSubTrack = false;
|
|
160
|
+
|
|
161
|
+
var currentAudioTrack = false;
|
|
162
|
+
|
|
163
|
+
var textTracks = [];
|
|
164
|
+
|
|
165
|
+
var audioTracks = [];
|
|
166
|
+
|
|
167
|
+
var count_message = 0;
|
|
168
|
+
|
|
169
|
+
var subtitleOffset = 5;
|
|
170
|
+
|
|
171
|
+
var setSubs = function (info) {
|
|
172
|
+
textTracks = [];
|
|
173
|
+
// console.log('sub tracks 1, nr of sub tracks: ', info.numSubtitleTracks);
|
|
174
|
+
if (info.numSubtitleTracks) {
|
|
175
|
+
|
|
176
|
+
// console.log('sub tracks 2');
|
|
177
|
+
|
|
178
|
+
// try {
|
|
179
|
+
// console.log('got sub info', JSON.stringify(info.subtitleTrackInfo));
|
|
180
|
+
// } catch(e) {};
|
|
181
|
+
for (var i = 0; i < info.subtitleTrackInfo.length; i++) {
|
|
182
|
+
var textTrack = info.subtitleTrackInfo[i];
|
|
183
|
+
textTrack.index = i;
|
|
184
|
+
var textTrackLang = textTrack.language === '(null)' ? '' : textTrack.language;
|
|
185
|
+
|
|
186
|
+
var textTrackId = 'EMBEDDED_' + textTrack.index;
|
|
187
|
+
|
|
188
|
+
if (!currentSubTrack && !textTracks.length) {
|
|
189
|
+
currentSubTrack = textTrackId;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
textTracks.push({
|
|
193
|
+
id: textTrackId,
|
|
194
|
+
lang: textTrackLang,
|
|
195
|
+
label: textTrackLang,
|
|
196
|
+
origin: 'EMBEDDED',
|
|
197
|
+
embedded: true,
|
|
198
|
+
mode: textTrackId === currentSubTrack ? 'showing' : 'disabled',
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// console.log('sub tracks all', textTracks);
|
|
204
|
+
|
|
205
|
+
onPropChanged('subtitlesTracks');
|
|
206
|
+
onPropChanged('selectedSubtitlesTrackId');
|
|
207
|
+
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
var setTracks = function (info) {
|
|
212
|
+
audioTracks = [];
|
|
213
|
+
// console.log('audio tracks 1, nr of audio tracks: ', info.numAudioTracks);
|
|
214
|
+
if (info.numAudioTracks) {
|
|
215
|
+
|
|
216
|
+
//console.log('audio tracks 2');
|
|
217
|
+
|
|
218
|
+
// try {
|
|
219
|
+
// console.log('got audio info', JSON.stringify(info.audioTrackInfo));
|
|
220
|
+
// } catch(e) {};
|
|
221
|
+
for (var i = 0; i < info.audioTrackInfo.length; i++) {
|
|
222
|
+
var audioTrack = info.audioTrackInfo[i];
|
|
223
|
+
audioTrack.index = i;
|
|
224
|
+
var audioTrackId = 'EMBEDDED_' + audioTrack.index;
|
|
225
|
+
if (!currentAudioTrack && !audioTracks.length) {
|
|
226
|
+
currentAudioTrack = audioTrackId;
|
|
227
|
+
}
|
|
228
|
+
var audioTrackLang = audioTrack.language === '(null)' ? '' : audioTrack.language;
|
|
229
|
+
audioTracks.push({
|
|
230
|
+
id: audioTrackId,
|
|
231
|
+
lang: audioTrackLang,
|
|
232
|
+
label: audioTrackLang,
|
|
233
|
+
origin: 'EMBEDDED',
|
|
234
|
+
embedded: true,
|
|
235
|
+
mode: audioTrackId === currentAudioTrack ? 'showing' : 'disabled',
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
// console.log('audio tracks all', audioTracks);
|
|
239
|
+
onPropChanged('audioTracks');
|
|
240
|
+
onPropChanged('selectedAudioTrackId');
|
|
241
|
+
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
var subscribe = function (cb) {
|
|
246
|
+
if (subscribed) return;
|
|
247
|
+
subscribed = true;
|
|
248
|
+
var answered = false;
|
|
249
|
+
// console.log('subscribing');
|
|
250
|
+
luna({
|
|
251
|
+
method: 'subscribe',
|
|
252
|
+
parameters: {
|
|
253
|
+
'mediaId': knownMediaId,
|
|
254
|
+
'subscribe': true
|
|
255
|
+
}
|
|
256
|
+
}, function (result) {
|
|
257
|
+
if (result.sourceInfo && !answered) {
|
|
258
|
+
answered = true;
|
|
259
|
+
// try {
|
|
260
|
+
// console.log('got source info', JSON.stringify(result.sourceInfo.programInfo[0]));
|
|
261
|
+
// } catch(e) {};
|
|
262
|
+
var info = result.sourceInfo.programInfo[0];
|
|
263
|
+
|
|
264
|
+
setSubs(info);
|
|
265
|
+
|
|
266
|
+
setTracks(info);
|
|
267
|
+
|
|
268
|
+
unsubscribe(cb);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if ((result.error || {}).errorCode) {
|
|
272
|
+
answered = true;
|
|
273
|
+
// console.error('luna playback error', result.error);
|
|
274
|
+
unsubscribe(cb);
|
|
275
|
+
// unsubscribe();
|
|
276
|
+
// onVideoError();
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if ((result.unloadCompleted || {}).mediaId === knownMediaId && (result.unloadCompleted || {}).state) {
|
|
281
|
+
// strange case where it just.. ends? without ever getting result.sourceInfo
|
|
282
|
+
// onEnded();
|
|
283
|
+
// console.log('strange case of end');
|
|
284
|
+
// unsubscribe(cb);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// console.log('WebOS', 'subscribe', JSON.stringify(result));
|
|
289
|
+
count_message++;
|
|
290
|
+
|
|
291
|
+
if (count_message === 30 && !answered) {
|
|
292
|
+
// cb();
|
|
293
|
+
unsubscribe(cb);
|
|
294
|
+
}
|
|
295
|
+
}, function() { // function(err)
|
|
296
|
+
// console.log('luna error log 2');
|
|
297
|
+
// console.error(err);
|
|
298
|
+
});
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
var unsubscribe = function (cb) {
|
|
302
|
+
if (!subscribed) return;
|
|
303
|
+
subscribed = false;
|
|
304
|
+
luna({
|
|
305
|
+
method: 'unsubscribe',
|
|
306
|
+
parameters: {
|
|
307
|
+
'mediaId': knownMediaId
|
|
308
|
+
}
|
|
309
|
+
}, function () { // function(result)
|
|
310
|
+
// console.log('unsubscribe result', JSON.stringify(result));
|
|
311
|
+
cb();
|
|
312
|
+
}, function () { // function(err)
|
|
313
|
+
// console.log('unsubscribe error', JSON.stringify(err));
|
|
314
|
+
cb();
|
|
315
|
+
});
|
|
316
|
+
cb();
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
// var unload = function (cb) {
|
|
320
|
+
// luna({
|
|
321
|
+
// method: 'unload',
|
|
322
|
+
// parameters: {
|
|
323
|
+
// 'mediaId': knownMediaId
|
|
324
|
+
// }
|
|
325
|
+
// }, cb, cb);
|
|
326
|
+
// };
|
|
327
|
+
|
|
328
|
+
var toggleSubtitles = function (status) {
|
|
329
|
+
if (!knownMediaId) return;
|
|
330
|
+
|
|
331
|
+
disabledSubs = !status;
|
|
332
|
+
|
|
333
|
+
// console.log('enable subs: ' + status);
|
|
334
|
+
|
|
335
|
+
luna({
|
|
336
|
+
method: 'setSubtitleEnable',
|
|
337
|
+
parameters: {
|
|
338
|
+
'mediaId': knownMediaId,
|
|
339
|
+
'enable': status
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
var styleElement = document.createElement('style');
|
|
345
|
+
containerElement.appendChild(styleElement);
|
|
346
|
+
styleElement.sheet.insertRule('video::cue { font-size: 4vmin; color: rgb(255, 255, 255); background-color: rgba(0, 0, 0, 0); text-shadow: rgb(34, 34, 34) 1px 1px 0.1em; }');
|
|
347
|
+
var videoElement = document.createElement('video');
|
|
348
|
+
videoElement.style.width = '100%';
|
|
349
|
+
videoElement.style.height = '100%';
|
|
350
|
+
videoElement.style.backgroundColor = 'black';
|
|
351
|
+
// videoElement.crossOrigin = 'anonymous';
|
|
352
|
+
videoElement.controls = false;
|
|
353
|
+
videoElement.onerror = function() {
|
|
354
|
+
onVideoError();
|
|
355
|
+
};
|
|
356
|
+
videoElement.onended = function() {
|
|
357
|
+
onEnded();
|
|
358
|
+
};
|
|
359
|
+
videoElement.onpause = function() {
|
|
360
|
+
onPropChanged('paused');
|
|
361
|
+
};
|
|
362
|
+
videoElement.onplay = function() {
|
|
363
|
+
onPropChanged('paused');
|
|
364
|
+
};
|
|
365
|
+
videoElement.ontimeupdate = function() {
|
|
366
|
+
onPropChanged('time');
|
|
367
|
+
onPropChanged('buffered');
|
|
368
|
+
};
|
|
369
|
+
videoElement.ondurationchange = function() {
|
|
370
|
+
onPropChanged('duration');
|
|
371
|
+
};
|
|
372
|
+
videoElement.onwaiting = function() {
|
|
373
|
+
onPropChanged('buffering');
|
|
374
|
+
onPropChanged('buffered');
|
|
375
|
+
};
|
|
376
|
+
videoElement.onseeking = function() {
|
|
377
|
+
onPropChanged('buffering');
|
|
378
|
+
onPropChanged('buffered');
|
|
379
|
+
};
|
|
380
|
+
videoElement.onseeked = function() {
|
|
381
|
+
onPropChanged('buffering');
|
|
382
|
+
onPropChanged('buffered');
|
|
383
|
+
};
|
|
384
|
+
videoElement.onstalled = function() {
|
|
385
|
+
onPropChanged('buffering');
|
|
386
|
+
onPropChanged('buffered');
|
|
387
|
+
};
|
|
388
|
+
videoElement.onplaying = function() {
|
|
389
|
+
onPropChanged('buffering');
|
|
390
|
+
onPropChanged('buffered');
|
|
391
|
+
};
|
|
392
|
+
videoElement.oncanplay = function() {
|
|
393
|
+
onPropChanged('buffering');
|
|
394
|
+
onPropChanged('buffered');
|
|
395
|
+
};
|
|
396
|
+
videoElement.canplaythrough = function() {
|
|
397
|
+
onPropChanged('buffering');
|
|
398
|
+
onPropChanged('buffered');
|
|
399
|
+
};
|
|
400
|
+
videoElement.onloadeddata = function() {
|
|
401
|
+
onPropChanged('buffering');
|
|
402
|
+
onPropChanged('buffered');
|
|
403
|
+
};
|
|
404
|
+
videoElement.onloadedmetadata = function() {
|
|
405
|
+
onPropChanged('buffering');
|
|
406
|
+
onPropChanged('buffered');
|
|
407
|
+
setProp('time', startTime);
|
|
408
|
+
};
|
|
409
|
+
videoElement.onvolumechange = function() {
|
|
410
|
+
onPropChanged('volume');
|
|
411
|
+
onPropChanged('muted');
|
|
412
|
+
};
|
|
413
|
+
videoElement.onratechange = function() {
|
|
414
|
+
onPropChanged('playbackSpeed');
|
|
415
|
+
};
|
|
416
|
+
videoElement.textTracks.onchange = function() {
|
|
417
|
+
onPropChanged('subtitlesTracks');
|
|
418
|
+
onPropChanged('selectedSubtitlesTrackId');
|
|
419
|
+
onCueChange();
|
|
420
|
+
Array.from(videoElement.textTracks).forEach(function(track) {
|
|
421
|
+
track.oncuechange = onCueChange;
|
|
422
|
+
});
|
|
423
|
+
};
|
|
424
|
+
containerElement.appendChild(videoElement);
|
|
425
|
+
|
|
426
|
+
var lastSubColor = null;
|
|
427
|
+
var lastSubBgColor = null;
|
|
428
|
+
var lastSubBgOpacity = 0;
|
|
429
|
+
var lastPlaybackSpeed = 1;
|
|
430
|
+
|
|
431
|
+
var events = new EventEmitter();
|
|
432
|
+
var destroyed = false;
|
|
433
|
+
var stream = null;
|
|
434
|
+
var startTime = null;
|
|
435
|
+
var subtitlesOffset = 0;
|
|
436
|
+
var observedProps = {
|
|
437
|
+
stream: false,
|
|
438
|
+
paused: false,
|
|
439
|
+
time: false,
|
|
440
|
+
duration: false,
|
|
441
|
+
buffering: false,
|
|
442
|
+
buffered: false,
|
|
443
|
+
subtitlesTracks: false,
|
|
444
|
+
selectedSubtitlesTrackId: false,
|
|
445
|
+
subtitlesOffset: false,
|
|
446
|
+
subtitlesSize: false,
|
|
447
|
+
subtitlesTextColor: false,
|
|
448
|
+
subtitlesBackgroundColor: false,
|
|
449
|
+
audioTracks: false,
|
|
450
|
+
selectedAudioTrackId: false,
|
|
451
|
+
volume: false,
|
|
452
|
+
muted: false,
|
|
453
|
+
playbackSpeed: false
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
function getProp(propName) {
|
|
457
|
+
switch (propName) {
|
|
458
|
+
case 'stream': {
|
|
459
|
+
return stream;
|
|
460
|
+
}
|
|
461
|
+
case 'paused': {
|
|
462
|
+
if (stream === null) {
|
|
463
|
+
return null;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
return !!videoElement.paused;
|
|
467
|
+
}
|
|
468
|
+
case 'time': {
|
|
469
|
+
if (stream === null || videoElement.currentTime === null || !isFinite(videoElement.currentTime)) {
|
|
470
|
+
return null;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
return Math.floor(videoElement.currentTime * 1000);
|
|
474
|
+
}
|
|
475
|
+
case 'duration': {
|
|
476
|
+
if (stream === null || videoElement.duration === null || !isFinite(videoElement.duration)) {
|
|
477
|
+
return null;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
return Math.floor(videoElement.duration * 1000);
|
|
481
|
+
}
|
|
482
|
+
case 'buffering': {
|
|
483
|
+
if (stream === null) {
|
|
484
|
+
return null;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
return videoElement.readyState < videoElement.HAVE_FUTURE_DATA;
|
|
488
|
+
}
|
|
489
|
+
case 'buffered': {
|
|
490
|
+
if (stream === null) {
|
|
491
|
+
return null;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
var time = videoElement.currentTime !== null && isFinite(videoElement.currentTime) ? videoElement.currentTime : 0;
|
|
495
|
+
for (var i = 0; i < videoElement.buffered.length; i++) {
|
|
496
|
+
if (videoElement.buffered.start(i) <= time && time <= videoElement.buffered.end(i)) {
|
|
497
|
+
return Math.floor(videoElement.buffered.end(i) * 1000);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
return Math.floor(time * 1000);
|
|
502
|
+
}
|
|
503
|
+
case 'subtitlesTracks': {
|
|
504
|
+
if (stream === null) {
|
|
505
|
+
return [];
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
return textTracks;
|
|
509
|
+
}
|
|
510
|
+
case 'selectedSubtitlesTrackId': {
|
|
511
|
+
if (stream === null || disabledSubs) {
|
|
512
|
+
return null;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
return currentSubTrack;
|
|
516
|
+
}
|
|
517
|
+
case 'subtitlesOffset': {
|
|
518
|
+
if (destroyed) {
|
|
519
|
+
return null;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
return subtitlesOffset;
|
|
523
|
+
}
|
|
524
|
+
case 'subtitlesSize': {
|
|
525
|
+
if (destroyed) {
|
|
526
|
+
return null;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return subSize;
|
|
530
|
+
}
|
|
531
|
+
case 'subtitlesTextColor': {
|
|
532
|
+
if (destroyed) {
|
|
533
|
+
return null;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
return lastSubColor || 'rgba(255, 255, 255, 255)';
|
|
537
|
+
}
|
|
538
|
+
case 'subtitlesBackgroundColor': {
|
|
539
|
+
if (destroyed) {
|
|
540
|
+
return null;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
return lastSubBgColor || 'rgba(255, 255, 255, 0)';
|
|
544
|
+
}
|
|
545
|
+
case 'audioTracks': {
|
|
546
|
+
return audioTracks;
|
|
547
|
+
}
|
|
548
|
+
case 'selectedAudioTrackId': {
|
|
549
|
+
return currentAudioTrack;
|
|
550
|
+
}
|
|
551
|
+
case 'volume': {
|
|
552
|
+
if (destroyed || videoElement.volume === null || !isFinite(videoElement.volume)) {
|
|
553
|
+
return null;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
return Math.floor(videoElement.volume * 100);
|
|
557
|
+
}
|
|
558
|
+
case 'muted': {
|
|
559
|
+
if (destroyed) {
|
|
560
|
+
return null;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
return !!videoElement.muted;
|
|
564
|
+
}
|
|
565
|
+
case 'playbackSpeed': {
|
|
566
|
+
if (destroyed || lastPlaybackSpeed === null || !isFinite(lastPlaybackSpeed)) {
|
|
567
|
+
return null;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
return lastPlaybackSpeed;
|
|
571
|
+
}
|
|
572
|
+
default: {
|
|
573
|
+
return null;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
function onCueChange() {
|
|
578
|
+
Array.from(videoElement.textTracks).forEach(function(track) {
|
|
579
|
+
Array.from(track.cues || []).forEach(function(cue) {
|
|
580
|
+
cue.snapToLines = false;
|
|
581
|
+
cue.line = 100 - subtitlesOffset;
|
|
582
|
+
});
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
function onVideoError() {
|
|
586
|
+
if (destroyed) {
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
var error;
|
|
591
|
+
switch ((videoElement.error || {}).code) {
|
|
592
|
+
case 1: {
|
|
593
|
+
error = ERROR.HTML_VIDEO.MEDIA_ERR_ABORTED;
|
|
594
|
+
break;
|
|
595
|
+
}
|
|
596
|
+
case 2: {
|
|
597
|
+
error = ERROR.HTML_VIDEO.MEDIA_ERR_NETWORK;
|
|
598
|
+
break;
|
|
599
|
+
}
|
|
600
|
+
case 3: {
|
|
601
|
+
error = ERROR.HTML_VIDEO.MEDIA_ERR_DECODE;
|
|
602
|
+
runWebOS({
|
|
603
|
+
need: 'com.webos.app.photovideo',
|
|
604
|
+
url: stream.url,
|
|
605
|
+
name: 'Stremio',
|
|
606
|
+
position: -1,
|
|
607
|
+
});
|
|
608
|
+
break;
|
|
609
|
+
}
|
|
610
|
+
case 4: {
|
|
611
|
+
error = ERROR.HTML_VIDEO.MEDIA_ERR_SRC_NOT_SUPPORTED;
|
|
612
|
+
runWebOS({
|
|
613
|
+
need: 'com.webos.app.photovideo',
|
|
614
|
+
url: stream.url,
|
|
615
|
+
name: 'Stremio',
|
|
616
|
+
position: -1,
|
|
617
|
+
});
|
|
618
|
+
break;
|
|
619
|
+
}
|
|
620
|
+
default: {
|
|
621
|
+
error = ERROR.UNKNOWN_ERROR;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
onError(Object.assign({}, error, {
|
|
625
|
+
critical: true,
|
|
626
|
+
error: videoElement.error
|
|
627
|
+
}));
|
|
628
|
+
}
|
|
629
|
+
function onError(error) {
|
|
630
|
+
events.emit('error', error);
|
|
631
|
+
if (error.critical) {
|
|
632
|
+
command('unload');
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
function onEnded() {
|
|
636
|
+
events.emit('ended');
|
|
637
|
+
}
|
|
638
|
+
function onPropChanged(propName) {
|
|
639
|
+
if (observedProps[propName]) {
|
|
640
|
+
events.emit('propChanged', propName, getProp(propName));
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
function observeProp(propName) {
|
|
644
|
+
if (observedProps.hasOwnProperty(propName)) {
|
|
645
|
+
events.emit('propValue', propName, getProp(propName));
|
|
646
|
+
observedProps[propName] = true;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
function setProp(propName, propValue) {
|
|
650
|
+
switch (propName) {
|
|
651
|
+
case 'paused': {
|
|
652
|
+
if (stream !== null) {
|
|
653
|
+
propValue ? videoElement.pause() : videoElement.play();
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
break;
|
|
657
|
+
}
|
|
658
|
+
case 'time': {
|
|
659
|
+
if (stream !== null && videoElement.readyState >= videoElement.HAVE_METADATA && propValue !== null && isFinite(propValue)) {
|
|
660
|
+
try {
|
|
661
|
+
videoElement.currentTime = parseInt(propValue, 10) / 1000;
|
|
662
|
+
} catch(e) {
|
|
663
|
+
// console.log('webos video change time error');
|
|
664
|
+
// console.error(e);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
break;
|
|
669
|
+
}
|
|
670
|
+
case 'selectedSubtitlesTrackId': {
|
|
671
|
+
if (stream !== null) {
|
|
672
|
+
if ((propValue || '').indexOf('EMBEDDED_') === 0) {
|
|
673
|
+
if (disabledSubs) {
|
|
674
|
+
toggleSubtitles(true);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// console.log('WebOS', 'change subtitles for id: ', knownMediaId, ' index:', propValue);
|
|
678
|
+
|
|
679
|
+
currentSubTrack = propValue;
|
|
680
|
+
var trackIndex = parseInt(propValue.replace('EMBEDDED_', ''));
|
|
681
|
+
// console.log('set subs to track idx: ' + trackIndex);
|
|
682
|
+
luna({
|
|
683
|
+
method: 'selectTrack',
|
|
684
|
+
parameters: {
|
|
685
|
+
'type': 'text',
|
|
686
|
+
'mediaId': knownMediaId,
|
|
687
|
+
'index': trackIndex
|
|
688
|
+
}
|
|
689
|
+
}, function() {
|
|
690
|
+
// console.log('changed subs track successfully');
|
|
691
|
+
var selectedSubtitlesTrack = getProp('subtitlesTracks')
|
|
692
|
+
.find(function(track) {
|
|
693
|
+
return track.id === propValue;
|
|
694
|
+
});
|
|
695
|
+
textTracks = textTracks.map(function(track) {
|
|
696
|
+
track.mode = track.id === currentSubTrack ? 'showing' : 'disabled';
|
|
697
|
+
return track;
|
|
698
|
+
});
|
|
699
|
+
if (selectedSubtitlesTrack) {
|
|
700
|
+
events.emit('subtitlesTrackLoaded', selectedSubtitlesTrack);
|
|
701
|
+
onPropChanged('selectedSubtitlesTrackId');
|
|
702
|
+
}
|
|
703
|
+
});
|
|
704
|
+
} else if (!propValue) {
|
|
705
|
+
toggleSubtitles(false);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
break;
|
|
710
|
+
}
|
|
711
|
+
case 'subtitlesOffset': {
|
|
712
|
+
if (propValue !== null && isFinite(propValue)) {
|
|
713
|
+
subtitlesOffset = Math.max(0, Math.min(100, parseInt(propValue, 10)));
|
|
714
|
+
var nextOffset = stremioSubOffsets(subtitleOffset);
|
|
715
|
+
if (nextOffset === false) { // use default
|
|
716
|
+
nextOffset = 0;
|
|
717
|
+
}
|
|
718
|
+
luna({
|
|
719
|
+
method: 'setSubtitlePosition',
|
|
720
|
+
parameters: {
|
|
721
|
+
'mediaId': knownMediaId,
|
|
722
|
+
'position': nextOffset,
|
|
723
|
+
}
|
|
724
|
+
}, function() {
|
|
725
|
+
// console.log('successfully changed sub offset to: ' + nextOffset);
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
onPropChanged('subtitlesOffset');
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
break;
|
|
732
|
+
}
|
|
733
|
+
case 'subtitlesSize': {
|
|
734
|
+
if (propValue !== null && isFinite(propValue)) {
|
|
735
|
+
subSize = Math.max(0, parseInt(propValue, 10));
|
|
736
|
+
var nextSubSize = stremioSubSizes(subSize);
|
|
737
|
+
if (nextSubSize === false) { // use default
|
|
738
|
+
nextSubSize = 2;
|
|
739
|
+
}
|
|
740
|
+
luna({
|
|
741
|
+
method: 'setSubtitleFontSize',
|
|
742
|
+
parameters: {
|
|
743
|
+
'mediaId': knownMediaId,
|
|
744
|
+
'fontSize': nextSubSize,
|
|
745
|
+
}
|
|
746
|
+
}, function() {
|
|
747
|
+
// console.log('successfully changed sub size to: ' + nextSubSize);
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
onPropChanged('subtitlesSize');
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
break;
|
|
754
|
+
}
|
|
755
|
+
case 'subtitlesTextColor': {
|
|
756
|
+
if (typeof propValue === 'string') {
|
|
757
|
+
// we use setSubtitleCharacterColor instead of setSubtitleColor
|
|
758
|
+
// because it has the same color options as the sub background
|
|
759
|
+
var nextColor = 'white';
|
|
760
|
+
if (stremioColors[propValue] && webOsColors.indexOf(stremioColors[propValue]) > -1) {
|
|
761
|
+
nextColor = stremioColors[propValue];
|
|
762
|
+
}
|
|
763
|
+
luna({
|
|
764
|
+
method: 'setSubtitleCharacterColor',
|
|
765
|
+
parameters: {
|
|
766
|
+
'mediaId': knownMediaId,
|
|
767
|
+
'charColor': nextColor,
|
|
768
|
+
}
|
|
769
|
+
}, function() {
|
|
770
|
+
// console.log('changed subtitle color successfully to: ' + nextColor);
|
|
771
|
+
});
|
|
772
|
+
lastSubColor = propValue;
|
|
773
|
+
onPropChanged('subtitlesTextColor');
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
break;
|
|
777
|
+
}
|
|
778
|
+
case 'subtitlesBackgroundColor': {
|
|
779
|
+
if (typeof propValue === 'string') {
|
|
780
|
+
if (stremioColors[propValue] && webOsColors.indexOf(stremioColors[propValue]) > -1) {
|
|
781
|
+
luna({
|
|
782
|
+
method: 'setSubtitleBackgroundColor',
|
|
783
|
+
parameters: {
|
|
784
|
+
'mediaId': knownMediaId,
|
|
785
|
+
'color': stremioColors[propValue],
|
|
786
|
+
}
|
|
787
|
+
}, function() {
|
|
788
|
+
// console.log('changed subtitle background color successfully to: ' + stremioColors[propValue]);
|
|
789
|
+
if (!lastSubBgOpacity) {
|
|
790
|
+
luna({
|
|
791
|
+
method: 'setSubtitleBackgroundOpacity',
|
|
792
|
+
parameters: {
|
|
793
|
+
'mediaId': knownMediaId,
|
|
794
|
+
'bgOpacity': 255,
|
|
795
|
+
}
|
|
796
|
+
}, function() {
|
|
797
|
+
// console.log('changed subtitle background opacity successfully to: ' + 255);
|
|
798
|
+
lastSubBgOpacity = 255;
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
});
|
|
802
|
+
} else {
|
|
803
|
+
// we don't know this color, set sub background opacity to 0
|
|
804
|
+
luna({
|
|
805
|
+
method: 'setSubtitleBackgroundOpacity',
|
|
806
|
+
parameters: {
|
|
807
|
+
'mediaId': knownMediaId,
|
|
808
|
+
'bgOpacity': 0,
|
|
809
|
+
}
|
|
810
|
+
}, function() {
|
|
811
|
+
// console.log('changed subtitle background opacity successfully to: ' + 0);
|
|
812
|
+
lastSubBgOpacity = 0;
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
lastSubBgColor = propValue;
|
|
816
|
+
onPropChanged('subtitlesBackgroundColor');
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
break;
|
|
820
|
+
}
|
|
821
|
+
case 'selectedAudioTrackId': {
|
|
822
|
+
// console.log('WebOS', 'change audio track for id: ', knownMediaId, ' index:', propValue);
|
|
823
|
+
|
|
824
|
+
if ((propValue || '').indexOf('EMBEDDED_') === 0) {
|
|
825
|
+
currentAudioTrack = propValue;
|
|
826
|
+
var trackIndex = parseInt(propValue.replace('EMBEDDED_', ''));
|
|
827
|
+
luna({
|
|
828
|
+
method: 'selectTrack',
|
|
829
|
+
parameters: {
|
|
830
|
+
'type': 'audio',
|
|
831
|
+
'mediaId': knownMediaId,
|
|
832
|
+
'index': trackIndex
|
|
833
|
+
}
|
|
834
|
+
}, function() {
|
|
835
|
+
// console.log('changed audio track successfully');
|
|
836
|
+
var selectedAudioTrack = getProp('audioTracks')
|
|
837
|
+
.find(function(track) {
|
|
838
|
+
return track.id === propValue;
|
|
839
|
+
});
|
|
840
|
+
|
|
841
|
+
audioTracks = audioTracks.map(function(track) {
|
|
842
|
+
track.mode = track.id === currentAudioTrack ? 'showing' : 'disabled';
|
|
843
|
+
return track;
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
if (selectedAudioTrack) {
|
|
847
|
+
events.emit('audioTrackLoaded', selectedAudioTrack);
|
|
848
|
+
onPropChanged('selectedAudioTrackId');
|
|
849
|
+
}
|
|
850
|
+
});
|
|
851
|
+
if (videoElement.audioTracks) {
|
|
852
|
+
for (var i = 0; i < videoElement.audioTracks.length; i++) {
|
|
853
|
+
videoElement.audioTracks[i].enabled = false;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
if(videoElement.audioTracks[trackIndex]) {
|
|
857
|
+
videoElement.audioTracks[trackIndex].enabled = true;
|
|
858
|
+
|
|
859
|
+
// console.log('WebOS', 'change audio two method:', trackIndex);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
break;
|
|
866
|
+
}
|
|
867
|
+
case 'volume': {
|
|
868
|
+
if (propValue !== null && isFinite(propValue)) {
|
|
869
|
+
videoElement.muted = false;
|
|
870
|
+
videoElement.volume = Math.max(0, Math.min(100, parseInt(propValue, 10))) / 100;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
break;
|
|
874
|
+
}
|
|
875
|
+
case 'muted': {
|
|
876
|
+
videoElement.muted = !!propValue;
|
|
877
|
+
break;
|
|
878
|
+
}
|
|
879
|
+
case 'playbackSpeed': {
|
|
880
|
+
// console.log('start change play rate to: ' + propValue);
|
|
881
|
+
// console.log(typeof propValue);
|
|
882
|
+
if (propValue !== null && isFinite(propValue)) {
|
|
883
|
+
lastPlaybackSpeed = parseFloat(propValue);
|
|
884
|
+
luna({
|
|
885
|
+
method: 'setPlayRate',
|
|
886
|
+
parameters: {
|
|
887
|
+
'mediaId': knownMediaId,
|
|
888
|
+
'playRate': lastPlaybackSpeed,
|
|
889
|
+
'audioOutput': true,
|
|
890
|
+
}
|
|
891
|
+
}, function() {
|
|
892
|
+
// console.log('set playback rate success: ', lastPlaybackSpeed);
|
|
893
|
+
}, function() {
|
|
894
|
+
// console.log('failed setting playback rate success: ', lastPlaybackSpeed);
|
|
895
|
+
});
|
|
896
|
+
onPropChanged('playbackSpeed');
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
break;
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
function command(commandName, commandArgs) {
|
|
904
|
+
switch (commandName) {
|
|
905
|
+
case 'load': {
|
|
906
|
+
// not sure about this
|
|
907
|
+
// command('unload');
|
|
908
|
+
if (commandArgs && commandArgs.stream && typeof commandArgs.stream.url === 'string') {
|
|
909
|
+
stream = commandArgs.stream;
|
|
910
|
+
startTime = commandArgs.time;
|
|
911
|
+
|
|
912
|
+
onPropChanged('stream');
|
|
913
|
+
videoElement.autoplay = typeof commandArgs.autoplay === 'boolean' ? commandArgs.autoplay : true;
|
|
914
|
+
|
|
915
|
+
onPropChanged('paused');
|
|
916
|
+
onPropChanged('time');
|
|
917
|
+
onPropChanged('duration');
|
|
918
|
+
onPropChanged('buffering');
|
|
919
|
+
onPropChanged('buffered');
|
|
920
|
+
onPropChanged('subtitlesTracks');
|
|
921
|
+
onPropChanged('selectedSubtitlesTrackId');
|
|
922
|
+
onPropChanged('audioTracks');
|
|
923
|
+
onPropChanged('selectedAudioTrackId');
|
|
924
|
+
|
|
925
|
+
var count = 0;
|
|
926
|
+
|
|
927
|
+
var initMediaId = function (cb) {
|
|
928
|
+
function retrieveMediaId() {
|
|
929
|
+
if (videoElement.mediaId) {
|
|
930
|
+
knownMediaId = videoElement.mediaId;
|
|
931
|
+
// console.log('got media id: ', videoElement.mediaId);
|
|
932
|
+
clearInterval(timer);
|
|
933
|
+
subscribe(cb);
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
count++;
|
|
937
|
+
if (count > 4) {
|
|
938
|
+
// console.log('failed to get media id');
|
|
939
|
+
clearInterval(timer);
|
|
940
|
+
cb();
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
var timer = setInterval(retrieveMediaId, 300);
|
|
944
|
+
};
|
|
945
|
+
|
|
946
|
+
var startVideo = function () {
|
|
947
|
+
// console.log('startVideo');
|
|
948
|
+
// not needed?
|
|
949
|
+
// videoElement.src = stream.url;
|
|
950
|
+
|
|
951
|
+
try {
|
|
952
|
+
videoElement.load();
|
|
953
|
+
} catch(e) {
|
|
954
|
+
// console.log('can\'t load video');
|
|
955
|
+
// console.error(e);
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
try {
|
|
959
|
+
// console.log('try play');
|
|
960
|
+
videoElement.play();
|
|
961
|
+
} catch(e) {
|
|
962
|
+
// console.log('can\'t start video');
|
|
963
|
+
// console.error(e);
|
|
964
|
+
}
|
|
965
|
+
};
|
|
966
|
+
|
|
967
|
+
videoElement.src = stream.url;
|
|
968
|
+
|
|
969
|
+
initMediaId(startVideo);
|
|
970
|
+
} else {
|
|
971
|
+
onError(Object.assign({}, ERROR.UNSUPPORTED_STREAM, {
|
|
972
|
+
critical: true,
|
|
973
|
+
stream: commandArgs ? commandArgs.stream : null
|
|
974
|
+
}));
|
|
975
|
+
}
|
|
976
|
+
break;
|
|
977
|
+
}
|
|
978
|
+
case 'unload': {
|
|
979
|
+
stream = null;
|
|
980
|
+
startTime = null;
|
|
981
|
+
Array.from(videoElement.textTracks).forEach(function(track) {
|
|
982
|
+
track.oncuechange = null;
|
|
983
|
+
});
|
|
984
|
+
videoElement.removeAttribute('src');
|
|
985
|
+
videoElement.load();
|
|
986
|
+
// not sure about this:
|
|
987
|
+
// try {
|
|
988
|
+
// videoElement.currentTime = 0;
|
|
989
|
+
// } catch(e) {
|
|
990
|
+
// console.log('webos video unload error');
|
|
991
|
+
// console.error(e);
|
|
992
|
+
// }
|
|
993
|
+
onPropChanged('stream');
|
|
994
|
+
onPropChanged('paused');
|
|
995
|
+
onPropChanged('time');
|
|
996
|
+
onPropChanged('duration');
|
|
997
|
+
onPropChanged('buffering');
|
|
998
|
+
onPropChanged('buffered');
|
|
999
|
+
onPropChanged('subtitlesTracks');
|
|
1000
|
+
onPropChanged('selectedSubtitlesTrackId');
|
|
1001
|
+
onPropChanged('audioTracks');
|
|
1002
|
+
onPropChanged('selectedAudioTrackId');
|
|
1003
|
+
// not sure about this:
|
|
1004
|
+
// unload(function() {});
|
|
1005
|
+
break;
|
|
1006
|
+
}
|
|
1007
|
+
case 'destroy': {
|
|
1008
|
+
command('unload');
|
|
1009
|
+
destroyed = true;
|
|
1010
|
+
onPropChanged('subtitlesOffset');
|
|
1011
|
+
onPropChanged('subtitlesSize');
|
|
1012
|
+
onPropChanged('subtitlesTextColor');
|
|
1013
|
+
onPropChanged('subtitlesBackgroundColor');
|
|
1014
|
+
onPropChanged('volume');
|
|
1015
|
+
onPropChanged('muted');
|
|
1016
|
+
onPropChanged('playbackSpeed');
|
|
1017
|
+
events.removeAllListeners();
|
|
1018
|
+
videoElement.onerror = null;
|
|
1019
|
+
videoElement.onended = null;
|
|
1020
|
+
videoElement.onpause = null;
|
|
1021
|
+
videoElement.onplay = null;
|
|
1022
|
+
videoElement.ontimeupdate = null;
|
|
1023
|
+
videoElement.ondurationchange = null;
|
|
1024
|
+
videoElement.onwaiting = null;
|
|
1025
|
+
videoElement.onseeking = null;
|
|
1026
|
+
videoElement.onseeked = null;
|
|
1027
|
+
videoElement.onstalled = null;
|
|
1028
|
+
videoElement.onplaying = null;
|
|
1029
|
+
videoElement.oncanplay = null;
|
|
1030
|
+
videoElement.canplaythrough = null;
|
|
1031
|
+
videoElement.onloadeddata = null;
|
|
1032
|
+
videoElement.onloadedmetadata = null;
|
|
1033
|
+
videoElement.onvolumechange = null;
|
|
1034
|
+
videoElement.onratechange = null;
|
|
1035
|
+
videoElement.textTracks.onchange = null;
|
|
1036
|
+
containerElement.removeChild(videoElement);
|
|
1037
|
+
containerElement.removeChild(styleElement);
|
|
1038
|
+
break;
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
this.on = function(eventName, listener) {
|
|
1044
|
+
if (destroyed) {
|
|
1045
|
+
throw new Error('Video is destroyed');
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
events.on(eventName, listener);
|
|
1049
|
+
};
|
|
1050
|
+
this.dispatch = function(action) {
|
|
1051
|
+
if (destroyed) {
|
|
1052
|
+
throw new Error('Video is destroyed');
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
if (action) {
|
|
1056
|
+
action = deepFreeze(cloneDeep(action));
|
|
1057
|
+
switch (action.type) {
|
|
1058
|
+
case 'observeProp': {
|
|
1059
|
+
observeProp(action.propName);
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
1062
|
+
case 'setProp': {
|
|
1063
|
+
setProp(action.propName, action.propValue);
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1066
|
+
case 'command': {
|
|
1067
|
+
command(action.commandName, action.commandArgs);
|
|
1068
|
+
return;
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
throw new Error('Invalid action dispatched: ' + JSON.stringify(action));
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
WebOsVideo.canPlayStream = function() { // function(stream)
|
|
1078
|
+
return Promise.resolve(true);
|
|
1079
|
+
};
|
|
1080
|
+
|
|
1081
|
+
WebOsVideo.manifest = {
|
|
1082
|
+
name: 'WebOsVideo',
|
|
1083
|
+
external: false,
|
|
1084
|
+
props: ['stream', 'paused', 'time', 'duration', 'buffering', 'buffered', 'audioTracks', 'selectedAudioTrackId', 'subtitlesTracks', 'selectedSubtitlesTrackId', 'subtitlesOffset', 'subtitlesSize', 'subtitlesTextColor', 'subtitlesBackgroundColor', 'volume', 'muted', 'playbackSpeed'],
|
|
1085
|
+
commands: ['load', 'unload', 'destroy'],
|
|
1086
|
+
events: ['propValue', 'propChanged', 'ended', 'error', 'subtitlesTrackLoaded', 'audioTrackLoaded']
|
|
1087
|
+
};
|
|
1088
|
+
|
|
1089
|
+
module.exports = WebOsVideo;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
var VIDEO_CODEC_CONFIGS = [
|
|
2
|
+
{
|
|
3
|
+
codec: 'h264',
|
|
4
|
+
mime: 'video/mp4; codecs="avc1.42E01E"',
|
|
5
|
+
},
|
|
6
|
+
{
|
|
7
|
+
codec: 'h265',
|
|
8
|
+
mime: 'video/mp4; codecs="hev1.1.6.L150.B0"',
|
|
9
|
+
aliases: ['hevc']
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
codec: 'vp8',
|
|
13
|
+
mime: 'video/mp4; codecs="vp8"'
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
codec: 'vp9',
|
|
17
|
+
mime: 'video/mp4; codecs="vp9"'
|
|
18
|
+
}
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
var AUDIO_CODEC_CONFIGS = [
|
|
22
|
+
{
|
|
23
|
+
codec: 'aac',
|
|
24
|
+
mime: 'audio/mp4; codecs="mp4a.40.2"'
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
codec: 'mp3',
|
|
28
|
+
mime: 'audio/mp4; codecs="mp3"'
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
codec: 'ac3',
|
|
32
|
+
mime: 'audio/mp4; codecs="ac-3"'
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
codec: 'eac3',
|
|
36
|
+
mime: 'audio/mp4; codecs="ec-3"'
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
codec: 'vorbis',
|
|
40
|
+
mime: 'audio/mp4; codecs="vorbis"'
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
codec: 'opus',
|
|
44
|
+
mime: 'audio/mp4; codecs="opus"'
|
|
45
|
+
}
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
function canPlay(config, options) {
|
|
49
|
+
return options.mediaElement.canPlayType(config.mime) ?
|
|
50
|
+
[config.codec].concat(config.aliases || [])
|
|
51
|
+
:
|
|
52
|
+
[];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function getMaxAudioChannels() {
|
|
56
|
+
if (/firefox/i.test(window.navigator.userAgent)) {
|
|
57
|
+
return 6;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!window.AudioContext) {
|
|
61
|
+
return 2;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
var maxChannelCount = new AudioContext().destination.maxChannelCount;
|
|
65
|
+
return maxChannelCount > 0 ? maxChannelCount : 2;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function getMediaCapabilities() {
|
|
69
|
+
var mediaElement = document.createElement('video');
|
|
70
|
+
var formats = ['mp4'];
|
|
71
|
+
var videoCodecs = VIDEO_CODEC_CONFIGS
|
|
72
|
+
.map(function(config) {
|
|
73
|
+
return canPlay(config, { mediaElement: mediaElement });
|
|
74
|
+
})
|
|
75
|
+
.reduce(function(result, value) {
|
|
76
|
+
return result.concat(value);
|
|
77
|
+
}, []);
|
|
78
|
+
var audioCodecs = AUDIO_CODEC_CONFIGS
|
|
79
|
+
.map(function(config) {
|
|
80
|
+
return canPlay(config, { mediaElement: mediaElement });
|
|
81
|
+
})
|
|
82
|
+
.reduce(function(result, value) {
|
|
83
|
+
return result.concat(value);
|
|
84
|
+
}, []);
|
|
85
|
+
var maxAudioChannels = getMaxAudioChannels();
|
|
86
|
+
return {
|
|
87
|
+
formats: formats,
|
|
88
|
+
videoCodecs: videoCodecs,
|
|
89
|
+
audioCodecs: audioCodecs,
|
|
90
|
+
maxAudioChannels: maxAudioChannels
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
module.exports = getMediaCapabilities();
|
|
@@ -3,6 +3,7 @@ var url = require('url');
|
|
|
3
3
|
var hat = require('hat');
|
|
4
4
|
var cloneDeep = require('lodash.clonedeep');
|
|
5
5
|
var deepFreeze = require('deep-freeze');
|
|
6
|
+
var mediaCapabilities = require('../mediaCapabilities');
|
|
6
7
|
var convertStream = require('./convertStream');
|
|
7
8
|
var ERROR = require('../error');
|
|
8
9
|
|
|
@@ -96,7 +97,29 @@ function withStreamingServer(Video) {
|
|
|
96
97
|
onPropChanged('stream');
|
|
97
98
|
convertStream(commandArgs.streamingServerURL, commandArgs.stream, commandArgs.seriesInfo)
|
|
98
99
|
.then(function(mediaURL) {
|
|
99
|
-
|
|
100
|
+
var formats = Array.isArray(commandArgs.formats) ?
|
|
101
|
+
commandArgs.formats
|
|
102
|
+
:
|
|
103
|
+
mediaCapabilities.formats;
|
|
104
|
+
var videoCodecs = Array.isArray(commandArgs.videoCodecs) ?
|
|
105
|
+
commandArgs.videoCodecs
|
|
106
|
+
:
|
|
107
|
+
mediaCapabilities.videoCodecs;
|
|
108
|
+
var audioCodecs = Array.isArray(commandArgs.audioCodecs) ?
|
|
109
|
+
commandArgs.audioCodecs
|
|
110
|
+
:
|
|
111
|
+
mediaCapabilities.audioCodecs;
|
|
112
|
+
var maxAudioChannels = commandArgs.maxAudioChannels !== null && isFinite(commandArgs.maxAudioChannels) ?
|
|
113
|
+
commandArgs.maxAudioChannels
|
|
114
|
+
:
|
|
115
|
+
mediaCapabilities.maxAudioChannels;
|
|
116
|
+
var canPlayStreamOptions = Object.assign({}, commandArgs, {
|
|
117
|
+
formats: formats,
|
|
118
|
+
videoCodecs: videoCodecs,
|
|
119
|
+
audioCodecs: audioCodecs,
|
|
120
|
+
maxAudioChannels: maxAudioChannels
|
|
121
|
+
});
|
|
122
|
+
return (commandArgs.forceTranscoding ? Promise.resolve(false) : VideoWithStreamingServer.canPlayStream({ url: mediaURL }, canPlayStreamOptions))
|
|
100
123
|
.catch(function(error) {
|
|
101
124
|
console.warn('Media probe error', error);
|
|
102
125
|
return false;
|
|
@@ -113,9 +136,16 @@ function withStreamingServer(Video) {
|
|
|
113
136
|
if (commandArgs.forceTranscoding) {
|
|
114
137
|
queryParams.set('forceTranscoding', '1');
|
|
115
138
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
139
|
+
|
|
140
|
+
videoCodecs.forEach(function(videoCodec) {
|
|
141
|
+
queryParams.append('videoCodecs', videoCodec);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
audioCodecs.forEach(function(audioCodec) {
|
|
145
|
+
queryParams.append('audioCodecs', audioCodec);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
queryParams.set('maxAudioChannels', maxAudioChannels);
|
|
119
149
|
|
|
120
150
|
return {
|
|
121
151
|
url: url.resolve(commandArgs.streamingServerURL, '/hlsv2/' + id + '/master.m3u8?' + queryParams.toString()),
|
|
@@ -269,8 +299,38 @@ function withStreamingServer(Video) {
|
|
|
269
299
|
};
|
|
270
300
|
}
|
|
271
301
|
|
|
272
|
-
VideoWithStreamingServer.canPlayStream = function(stream) {
|
|
273
|
-
return Video.canPlayStream(stream)
|
|
302
|
+
VideoWithStreamingServer.canPlayStream = function(stream, options) {
|
|
303
|
+
return Video.canPlayStream(stream)
|
|
304
|
+
.then(function(canPlay) {
|
|
305
|
+
if (!canPlay) {
|
|
306
|
+
throw new Error('Fallback using /hlsv2/probe');
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return canPlay;
|
|
310
|
+
})
|
|
311
|
+
.catch(function() {
|
|
312
|
+
var queryParams = new URLSearchParams([['mediaURL', stream.url]]);
|
|
313
|
+
return fetch(url.resolve(options.streamingServerURL, '/hlsv2/probe?' + queryParams.toString()))
|
|
314
|
+
.then(function(resp) {
|
|
315
|
+
return resp.json();
|
|
316
|
+
})
|
|
317
|
+
.then(function(probe) {
|
|
318
|
+
var isFormatSupported = options.formats.some(function(format) {
|
|
319
|
+
return probe.format.name.indexOf(format) !== -1;
|
|
320
|
+
});
|
|
321
|
+
var areStreamsSupported = probe.streams.every(function(stream) {
|
|
322
|
+
if (stream.track === 'audio') {
|
|
323
|
+
return stream.channels <= options.maxAudioChannels &&
|
|
324
|
+
options.audioCodecs.indexOf(stream.codec) !== -1;
|
|
325
|
+
} else if (stream.track === 'video') {
|
|
326
|
+
return options.videoCodecs.indexOf(stream.codec) !== -1;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return true;
|
|
330
|
+
});
|
|
331
|
+
return isFormatSupported && areStreamsSupported;
|
|
332
|
+
});
|
|
333
|
+
});
|
|
274
334
|
};
|
|
275
335
|
|
|
276
336
|
VideoWithStreamingServer.manifest = {
|