@stremio/stremio-video 0.0.24 → 0.0.25-rc.2
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 +1 -1
- package/src/ChromecastSenderVideo/ChromecastSenderVideo.js +3 -1
- package/src/StremioVideo/selectVideoImplementation.js +6 -5
- package/src/TizenVideo/TizenVideo.js +2 -2
- package/src/WebOsVideo/WebOsVideo.js +22 -19
- package/src/withStreamingServer/convertStream.js +5 -5
- package/src/withStreamingServer/createTorrent.js +9 -4
- package/src/withStreamingServer/fetchVideoParams.js +95 -0
- package/src/withStreamingServer/withStreamingServer.js +63 -22
- package/src/withVideoParams/index.js +3 -0
- package/src/withVideoParams/withVideoParams.js +143 -0
package/package.json
CHANGED
|
@@ -63,6 +63,7 @@ function ChromecastSenderVideo(options) {
|
|
|
63
63
|
volume: false,
|
|
64
64
|
muted: false,
|
|
65
65
|
playbackSpeed: false,
|
|
66
|
+
videoParams: false,
|
|
66
67
|
extraSubtitlesTracks: false,
|
|
67
68
|
selectedExtraSubtitlesTrackId: false,
|
|
68
69
|
extraSubtitlesDelay: false,
|
|
@@ -126,6 +127,7 @@ function ChromecastSenderVideo(options) {
|
|
|
126
127
|
onPropChanged('volume', null);
|
|
127
128
|
onPropChanged('muted', null);
|
|
128
129
|
onPropChanged('playbackSpeed', null);
|
|
130
|
+
onPropChanged('videoParams', null);
|
|
129
131
|
onPropChanged('extraSubtitlesTracks', []);
|
|
130
132
|
onPropChanged('selectedExtraSubtitlesTrackId', null);
|
|
131
133
|
onPropChanged('extraSubtitlesDelay', null);
|
|
@@ -190,7 +192,7 @@ ChromecastSenderVideo.canPlayStream = function() {
|
|
|
190
192
|
ChromecastSenderVideo.manifest = {
|
|
191
193
|
name: 'ChromecastSenderVideo',
|
|
192
194
|
external: true,
|
|
193
|
-
props: ['stream', 'loaded', 'paused', 'time', 'duration', 'buffering', 'buffered', 'audioTracks', 'selectedAudioTrackId', 'subtitlesTracks', 'selectedSubtitlesTrackId', 'subtitlesOffset', 'subtitlesSize', 'subtitlesTextColor', 'subtitlesBackgroundColor', 'subtitlesOutlineColor', 'volume', 'muted', 'playbackSpeed', 'extraSubtitlesTracks', 'selectedExtraSubtitlesTrackId', 'extraSubtitlesDelay', 'extraSubtitlesSize', 'extraSubtitlesOffset', 'extraSubtitlesTextColor', 'extraSubtitlesBackgroundColor', 'extraSubtitlesOutlineColor'],
|
|
195
|
+
props: ['stream', 'loaded', 'paused', 'time', 'duration', 'buffering', 'buffered', 'audioTracks', 'selectedAudioTrackId', 'subtitlesTracks', 'selectedSubtitlesTrackId', 'subtitlesOffset', 'subtitlesSize', 'subtitlesTextColor', 'subtitlesBackgroundColor', 'subtitlesOutlineColor', 'volume', 'muted', 'playbackSpeed', 'videoParams', 'extraSubtitlesTracks', 'selectedExtraSubtitlesTrackId', 'extraSubtitlesDelay', 'extraSubtitlesSize', 'extraSubtitlesOffset', 'extraSubtitlesTextColor', 'extraSubtitlesBackgroundColor', 'extraSubtitlesOutlineColor'],
|
|
194
196
|
commands: ['load', 'unload', 'destroy', 'addExtraSubtitlesTracks'],
|
|
195
197
|
events: ['propValue', 'propChanged', 'ended', 'error', 'subtitlesTrackLoaded', 'audioTrackLoaded', 'extraSubtitlesTrackLoaded', 'implementationChanged']
|
|
196
198
|
};
|
|
@@ -7,6 +7,7 @@ var IFrameVideo = require('../IFrameVideo');
|
|
|
7
7
|
var YouTubeVideo = require('../YouTubeVideo');
|
|
8
8
|
var withStreamingServer = require('../withStreamingServer');
|
|
9
9
|
var withHTMLSubtitles = require('../withHTMLSubtitles');
|
|
10
|
+
var withVideoParams = require('../withVideoParams');
|
|
10
11
|
|
|
11
12
|
function selectVideoImplementation(commandArgs, options) {
|
|
12
13
|
if (!commandArgs.stream || typeof commandArgs.stream.externalUrl === 'string') {
|
|
@@ -18,11 +19,11 @@ function selectVideoImplementation(commandArgs, options) {
|
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
if (typeof commandArgs.stream.ytId === 'string') {
|
|
21
|
-
return withHTMLSubtitles(YouTubeVideo);
|
|
22
|
+
return withVideoParams(withHTMLSubtitles(YouTubeVideo));
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
if (typeof commandArgs.stream.playerFrameUrl === 'string') {
|
|
25
|
-
return IFrameVideo;
|
|
26
|
+
return withVideoParams(IFrameVideo);
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
if (options.shellTransport) {
|
|
@@ -41,12 +42,12 @@ function selectVideoImplementation(commandArgs, options) {
|
|
|
41
42
|
|
|
42
43
|
if (typeof commandArgs.stream.url === 'string') {
|
|
43
44
|
if (typeof global.webOS !== 'undefined') {
|
|
44
|
-
return withHTMLSubtitles(WebOsVideo);
|
|
45
|
+
return withVideoParams(withHTMLSubtitles(WebOsVideo));
|
|
45
46
|
}
|
|
46
47
|
if (typeof global.tizen !== 'undefined') {
|
|
47
|
-
return withHTMLSubtitles(TizenVideo);
|
|
48
|
+
return withVideoParams(withHTMLSubtitles(TizenVideo));
|
|
48
49
|
}
|
|
49
|
-
return withHTMLSubtitles(HTMLVideo);
|
|
50
|
+
return withVideoParams(withHTMLSubtitles(HTMLVideo));
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
return null;
|
|
@@ -204,7 +204,7 @@ function TizenVideo(options) {
|
|
|
204
204
|
try {
|
|
205
205
|
extra = JSON.parse(textTrack.extra_info);
|
|
206
206
|
} catch(e) {}
|
|
207
|
-
var textTrackLang =
|
|
207
|
+
var textTrackLang = typeof extra.track_lang === 'string' && extra.track_lang.length > 0 ? extra.track_lang.trim() : null;
|
|
208
208
|
textTracks.push({
|
|
209
209
|
id: textTrackId,
|
|
210
210
|
lang: textTrackLang,
|
|
@@ -291,7 +291,7 @@ function TizenVideo(options) {
|
|
|
291
291
|
try {
|
|
292
292
|
extra = JSON.parse(audioTrack.extra_info);
|
|
293
293
|
} catch(e) {}
|
|
294
|
-
var audioTrackLang = extra.language
|
|
294
|
+
var audioTrackLang = typeof extra.language === 'string' && extra.language.length > 0 ? extra.language : null;
|
|
295
295
|
audioTracks.push({
|
|
296
296
|
id: audioTrackId,
|
|
297
297
|
lang: audioTrackLang,
|
|
@@ -15,12 +15,11 @@ function luna(params, call, fail, method) {
|
|
|
15
15
|
window.webOS.service.request(method || 'luna://com.webos.media', params);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
function
|
|
19
|
-
// console.log('run web os', params);
|
|
18
|
+
function launchVideoApp(params, success, failure) {
|
|
20
19
|
window.webOS.service.request('luna://com.webos.applicationManager', {
|
|
21
20
|
method: 'launch',
|
|
22
21
|
parameters: {
|
|
23
|
-
'id': params.
|
|
22
|
+
'id': params.id,
|
|
24
23
|
'params': {
|
|
25
24
|
'payload':[
|
|
26
25
|
{
|
|
@@ -46,19 +45,17 @@ function runWebOS(params, failed) {
|
|
|
46
45
|
}
|
|
47
46
|
},
|
|
48
47
|
onSuccess: function () {
|
|
49
|
-
|
|
48
|
+
success && success();
|
|
50
49
|
},
|
|
51
|
-
onFailure: function () {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (params.
|
|
55
|
-
params.
|
|
56
|
-
|
|
57
|
-
} else if(params.
|
|
58
|
-
params.
|
|
59
|
-
|
|
60
|
-
} else if (params.need === 'com.webos.app.mediadiscovery') {
|
|
61
|
-
failed();
|
|
50
|
+
onFailure: function () {
|
|
51
|
+
failure && failure(new Error('Failed to launch' + params.id));
|
|
52
|
+
|
|
53
|
+
if (params.id === 'com.webos.app.photovideo') {
|
|
54
|
+
params.id = 'com.webos.app.smartshare';
|
|
55
|
+
launchVideoApp(params, success, failure);
|
|
56
|
+
} else if(params.id === 'com.webos.app.smartshare') {
|
|
57
|
+
params.id = 'com.webos.app.mediadiscovery';
|
|
58
|
+
launchVideoApp(params, success, failure);
|
|
62
59
|
}
|
|
63
60
|
}
|
|
64
61
|
});
|
|
@@ -599,21 +596,27 @@ function WebOsVideo(options) {
|
|
|
599
596
|
}
|
|
600
597
|
case 3: {
|
|
601
598
|
error = ERROR.HTML_VIDEO.MEDIA_ERR_DECODE;
|
|
602
|
-
|
|
603
|
-
|
|
599
|
+
launchVideoApp({
|
|
600
|
+
id: 'com.webos.app.photovideo',
|
|
604
601
|
url: stream.url,
|
|
605
602
|
name: 'Stremio',
|
|
606
603
|
position: -1,
|
|
604
|
+
}, null, function(e) {
|
|
605
|
+
// eslint-disable-next-line no-console
|
|
606
|
+
console.error(e);
|
|
607
607
|
});
|
|
608
608
|
break;
|
|
609
609
|
}
|
|
610
610
|
case 4: {
|
|
611
611
|
error = ERROR.HTML_VIDEO.MEDIA_ERR_SRC_NOT_SUPPORTED;
|
|
612
|
-
|
|
613
|
-
|
|
612
|
+
launchVideoApp({
|
|
613
|
+
id: 'com.webos.app.photovideo',
|
|
614
614
|
url: stream.url,
|
|
615
615
|
name: 'Stremio',
|
|
616
616
|
position: -1,
|
|
617
|
+
}, null, function(e) {
|
|
618
|
+
// eslint-disable-next-line no-console
|
|
619
|
+
console.error(e);
|
|
617
620
|
});
|
|
618
621
|
break;
|
|
619
622
|
}
|
|
@@ -23,14 +23,14 @@ function convertStream(streamingServerURL, stream, seriesInfo) {
|
|
|
23
23
|
:
|
|
24
24
|
[];
|
|
25
25
|
createTorrent(streamingServerURL, parsedMagnetURI.infoHash, null, sources, seriesInfo)
|
|
26
|
-
.then(function(
|
|
27
|
-
resolve(url);
|
|
26
|
+
.then(function(torrent) {
|
|
27
|
+
resolve({ url: torrent.url, infoHash: torrent.infoHash, fileIdx: torrent.fileIdx });
|
|
28
28
|
})
|
|
29
29
|
.catch(function(error) {
|
|
30
30
|
reject(error);
|
|
31
31
|
});
|
|
32
32
|
} else {
|
|
33
|
-
resolve(stream.url);
|
|
33
|
+
resolve({ url: stream.url });
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
return;
|
|
@@ -38,8 +38,8 @@ function convertStream(streamingServerURL, stream, seriesInfo) {
|
|
|
38
38
|
|
|
39
39
|
if (typeof stream.infoHash === 'string') {
|
|
40
40
|
createTorrent(streamingServerURL, stream.infoHash, stream.fileIdx, stream.announce, seriesInfo)
|
|
41
|
-
.then(function(
|
|
42
|
-
resolve(url);
|
|
41
|
+
.then(function(torrent) {
|
|
42
|
+
resolve({ url: torrent.url, infoHash: torrent.infoHash, fileIdx: torrent.fileIdx });
|
|
43
43
|
})
|
|
44
44
|
.catch(function(error) {
|
|
45
45
|
reject(error);
|
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
var url = require('url');
|
|
2
2
|
|
|
3
|
-
function
|
|
3
|
+
function buildTorrent(streamingServerURL, infoHash, fileIdx, sources) {
|
|
4
4
|
var query = Array.isArray(sources) && sources.length > 0 ?
|
|
5
5
|
'?' + new URLSearchParams(sources.map(function(source) {
|
|
6
6
|
return ['tr', source];
|
|
7
7
|
}))
|
|
8
8
|
:
|
|
9
9
|
'';
|
|
10
|
-
return
|
|
10
|
+
return {
|
|
11
|
+
url: url.resolve(streamingServerURL, '/' + encodeURIComponent(infoHash) + '/' + encodeURIComponent(fileIdx)) + query,
|
|
12
|
+
infoHash: infoHash,
|
|
13
|
+
fileIdx: fileIdx,
|
|
14
|
+
sources: sources
|
|
15
|
+
};
|
|
11
16
|
}
|
|
12
17
|
|
|
13
18
|
function createTorrent(streamingServerURL, infoHash, fileIdx, sources, seriesInfo) {
|
|
14
19
|
if ((!Array.isArray(sources) || sources.length === 0) && (fileIdx !== null && isFinite(fileIdx))) {
|
|
15
|
-
return Promise.resolve(
|
|
20
|
+
return Promise.resolve(buildTorrent(streamingServerURL, infoHash, fileIdx, sources));
|
|
16
21
|
}
|
|
17
22
|
|
|
18
23
|
var body = {
|
|
@@ -58,7 +63,7 @@ function createTorrent(streamingServerURL, infoHash, fileIdx, sources, seriesInf
|
|
|
58
63
|
|
|
59
64
|
throw new Error(resp.status + ' (' + resp.statusText + ')');
|
|
60
65
|
}).then(function(resp) {
|
|
61
|
-
return
|
|
66
|
+
return buildTorrent(streamingServerURL, infoHash, body.guessFileIdx ? resp.guessedFileIdx : fileIdx, body.peerSearch ? body.peerSearch.sources : []);
|
|
62
67
|
});
|
|
63
68
|
}
|
|
64
69
|
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
var url = require('url');
|
|
2
|
+
|
|
3
|
+
function fetchOpensubtitlesParams(streamingServerURL, mediaURL, behaviorHints) {
|
|
4
|
+
var hash = behaviorHints && typeof behaviorHints.videoHash === 'string' ? behaviorHints.videoHash : null;
|
|
5
|
+
var size = behaviorHints && isFinite(behaviorHints.videoSize) ? behaviorHints.videoSize : null;
|
|
6
|
+
if (typeof hash === 'string' && size !== null && isFinite(size)) {
|
|
7
|
+
return Promise.resolve({ hash: hash, size: size });
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
var queryParams = new URLSearchParams([['videoUrl', mediaURL]]);
|
|
11
|
+
return fetch(url.resolve(streamingServerURL, '/opensubHash?' + queryParams.toString()))
|
|
12
|
+
.then(function(resp) {
|
|
13
|
+
if (resp.ok) {
|
|
14
|
+
return resp.json();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
throw new Error(resp.status + ' (' + resp.statusText + ')');
|
|
18
|
+
})
|
|
19
|
+
.then(function(resp) {
|
|
20
|
+
if (resp.error) {
|
|
21
|
+
throw new Error(resp.error);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
hash: typeof hash === 'string' ?
|
|
26
|
+
hash
|
|
27
|
+
:
|
|
28
|
+
resp.result && typeof resp.result.hash === 'string' ?
|
|
29
|
+
resp.result.hash
|
|
30
|
+
:
|
|
31
|
+
null,
|
|
32
|
+
size: size !== null && isFinite(size) ?
|
|
33
|
+
size
|
|
34
|
+
:
|
|
35
|
+
resp.result && typeof resp.result.size ?
|
|
36
|
+
resp.result.size
|
|
37
|
+
:
|
|
38
|
+
null
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function fetchFilename(streamingServerURL, mediaURL, infoHash, fileIdx, behaviorHints) {
|
|
44
|
+
if (behaviorHints && typeof behaviorHints.filename === 'string') {
|
|
45
|
+
return Promise.resolve(behaviorHints.filename);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (infoHash) {
|
|
49
|
+
return fetch(url.resolve(streamingServerURL, '/' + encodeURIComponent(infoHash) + '/' + encodeURIComponent(fileIdx) + '/stats.json'))
|
|
50
|
+
.then(function(resp) {
|
|
51
|
+
if (resp.ok) {
|
|
52
|
+
return resp.json();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
throw new Error(resp.status + ' (' + resp.statusText + ')');
|
|
56
|
+
})
|
|
57
|
+
.then(function(resp) {
|
|
58
|
+
if (!resp || typeof resp.streamName !== 'string') {
|
|
59
|
+
throw new Error('Could not retrieve filename from torrent');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return resp.streamName;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return Promise.resolve(decodeURIComponent(mediaURL.split('/').pop()));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function fetchVideoParams(streamingServerURL, mediaURL, infoHash, fileIdx, behaviorHints) {
|
|
70
|
+
return Promise.allSettled([
|
|
71
|
+
fetchOpensubtitlesParams(streamingServerURL, mediaURL, behaviorHints),
|
|
72
|
+
fetchFilename(streamingServerURL, mediaURL, infoHash, fileIdx, behaviorHints)
|
|
73
|
+
]).then(function(results) {
|
|
74
|
+
var result = { hash: null, size: null, filename: null };
|
|
75
|
+
|
|
76
|
+
if (results[0].status === 'fulfilled') {
|
|
77
|
+
result.hash = results[0].value.hash;
|
|
78
|
+
result.size = results[0].value.size;
|
|
79
|
+
} else if (results[0].reason) {
|
|
80
|
+
// eslint-disable-next-line no-console
|
|
81
|
+
console.error(results[0].reason);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (results[1].status === 'fulfilled') {
|
|
85
|
+
result.filename = results[1].value;
|
|
86
|
+
} else if (results[1].reason) {
|
|
87
|
+
// eslint-disable-next-line no-console
|
|
88
|
+
console.error(results[1].reason);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return result;
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
module.exports = fetchVideoParams;
|
|
@@ -5,6 +5,7 @@ var cloneDeep = require('lodash.clonedeep');
|
|
|
5
5
|
var deepFreeze = require('deep-freeze');
|
|
6
6
|
var mediaCapabilities = require('../mediaCapabilities');
|
|
7
7
|
var convertStream = require('./convertStream');
|
|
8
|
+
var fetchVideoParams = require('./fetchVideoParams');
|
|
8
9
|
var ERROR = require('../error');
|
|
9
10
|
|
|
10
11
|
function withStreamingServer(Video) {
|
|
@@ -27,10 +28,12 @@ function withStreamingServer(Video) {
|
|
|
27
28
|
var loadArgs = null;
|
|
28
29
|
var loaded = false;
|
|
29
30
|
var actionsQueue = [];
|
|
31
|
+
var videoParams = null;
|
|
30
32
|
var events = new EventEmitter();
|
|
31
33
|
var destroyed = false;
|
|
32
34
|
var observedProps = {
|
|
33
|
-
stream: false
|
|
35
|
+
stream: false,
|
|
36
|
+
videoParams: false
|
|
34
37
|
};
|
|
35
38
|
|
|
36
39
|
function flushActionsQueue() {
|
|
@@ -70,6 +73,9 @@ function withStreamingServer(Video) {
|
|
|
70
73
|
case 'stream': {
|
|
71
74
|
return loadArgs !== null ? loadArgs.stream : null;
|
|
72
75
|
}
|
|
76
|
+
case 'videoParams': {
|
|
77
|
+
return videoParams;
|
|
78
|
+
}
|
|
73
79
|
default: {
|
|
74
80
|
return videoPropValue;
|
|
75
81
|
}
|
|
@@ -77,7 +83,8 @@ function withStreamingServer(Video) {
|
|
|
77
83
|
}
|
|
78
84
|
function observeProp(propName) {
|
|
79
85
|
switch (propName) {
|
|
80
|
-
case 'stream':
|
|
86
|
+
case 'stream':
|
|
87
|
+
case 'videoParams': {
|
|
81
88
|
events.emit('propValue', propName, getProp(propName, null));
|
|
82
89
|
observedProps[propName] = true;
|
|
83
90
|
return true;
|
|
@@ -96,7 +103,10 @@ function withStreamingServer(Video) {
|
|
|
96
103
|
loadArgs = commandArgs;
|
|
97
104
|
onPropChanged('stream');
|
|
98
105
|
convertStream(commandArgs.streamingServerURL, commandArgs.stream, commandArgs.seriesInfo)
|
|
99
|
-
.then(function(
|
|
106
|
+
.then(function(result) {
|
|
107
|
+
var mediaURL = result.url;
|
|
108
|
+
var infoHash = result.infoHash;
|
|
109
|
+
var fileIdx = result.fileIdx;
|
|
100
110
|
var formats = Array.isArray(commandArgs.formats) ?
|
|
101
111
|
commandArgs.formats
|
|
102
112
|
:
|
|
@@ -127,7 +137,12 @@ function withStreamingServer(Video) {
|
|
|
127
137
|
.then(function(canPlay) {
|
|
128
138
|
if (canPlay) {
|
|
129
139
|
return {
|
|
130
|
-
|
|
140
|
+
mediaURL: mediaURL,
|
|
141
|
+
infoHash: infoHash,
|
|
142
|
+
fileIdx: fileIdx,
|
|
143
|
+
stream: {
|
|
144
|
+
url: mediaURL
|
|
145
|
+
}
|
|
131
146
|
};
|
|
132
147
|
}
|
|
133
148
|
|
|
@@ -148,27 +163,32 @@ function withStreamingServer(Video) {
|
|
|
148
163
|
queryParams.set('maxAudioChannels', maxAudioChannels);
|
|
149
164
|
|
|
150
165
|
return {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
+
mediaURL: mediaURL,
|
|
167
|
+
infoHash: infoHash,
|
|
168
|
+
fileIdx: fileIdx,
|
|
169
|
+
stream: {
|
|
170
|
+
url: url.resolve(commandArgs.streamingServerURL, '/hlsv2/' + id + '/master.m3u8?' + queryParams.toString()),
|
|
171
|
+
subtitles: Array.isArray(commandArgs.stream.subtitles) ?
|
|
172
|
+
commandArgs.stream.subtitles.map(function(track) {
|
|
173
|
+
return Object.assign({}, track, {
|
|
174
|
+
url: typeof track.url === 'string' ?
|
|
175
|
+
url.resolve(commandArgs.streamingServerURL, '/subtitles.vtt?' + new URLSearchParams([['from', track.url]]).toString())
|
|
176
|
+
:
|
|
177
|
+
track.url
|
|
178
|
+
});
|
|
179
|
+
})
|
|
180
|
+
:
|
|
181
|
+
[],
|
|
182
|
+
behaviorHints: {
|
|
183
|
+
headers: {
|
|
184
|
+
'content-type': 'application/vnd.apple.mpegurl'
|
|
185
|
+
}
|
|
166
186
|
}
|
|
167
187
|
}
|
|
168
188
|
};
|
|
169
189
|
});
|
|
170
190
|
})
|
|
171
|
-
.then(function(
|
|
191
|
+
.then(function(result) {
|
|
172
192
|
if (commandArgs !== loadArgs) {
|
|
173
193
|
return;
|
|
174
194
|
}
|
|
@@ -177,11 +197,30 @@ function withStreamingServer(Video) {
|
|
|
177
197
|
type: 'command',
|
|
178
198
|
commandName: 'load',
|
|
179
199
|
commandArgs: Object.assign({}, commandArgs, {
|
|
180
|
-
stream: stream
|
|
200
|
+
stream: result.stream
|
|
181
201
|
})
|
|
182
202
|
});
|
|
183
203
|
loaded = true;
|
|
184
204
|
flushActionsQueue();
|
|
205
|
+
fetchVideoParams(commandArgs.streamingServerURL, result.mediaURL, result.infoHash, result.fileIdx, commandArgs.stream.behaviorHints)
|
|
206
|
+
.then(function(result) {
|
|
207
|
+
if (commandArgs !== loadArgs) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
videoParams = result;
|
|
212
|
+
onPropChanged('videoParams');
|
|
213
|
+
})
|
|
214
|
+
.catch(function(error) {
|
|
215
|
+
if (commandArgs !== loadArgs) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// eslint-disable-next-line no-console
|
|
220
|
+
console.error(error);
|
|
221
|
+
videoParams = { hash: null, size: null, filename: null };
|
|
222
|
+
onPropChanged('videoParams');
|
|
223
|
+
});
|
|
185
224
|
})
|
|
186
225
|
.catch(function(error) {
|
|
187
226
|
if (commandArgs !== loadArgs) {
|
|
@@ -237,7 +276,9 @@ function withStreamingServer(Video) {
|
|
|
237
276
|
loadArgs = null;
|
|
238
277
|
loaded = false;
|
|
239
278
|
actionsQueue = [];
|
|
279
|
+
videoParams = null;
|
|
240
280
|
onPropChanged('stream');
|
|
281
|
+
onPropChanged('videoParams');
|
|
241
282
|
return false;
|
|
242
283
|
}
|
|
243
284
|
case 'destroy': {
|
|
@@ -336,7 +377,7 @@ function withStreamingServer(Video) {
|
|
|
336
377
|
VideoWithStreamingServer.manifest = {
|
|
337
378
|
name: Video.manifest.name + 'WithStreamingServer',
|
|
338
379
|
external: Video.manifest.external,
|
|
339
|
-
props: Video.manifest.props.concat(['stream'])
|
|
380
|
+
props: Video.manifest.props.concat(['stream', 'videoParams'])
|
|
340
381
|
.filter(function(value, index, array) { return array.indexOf(value) === index; }),
|
|
341
382
|
commands: Video.manifest.commands.concat(['load', 'unload', 'destroy', 'addExtraSubtitlesTracks'])
|
|
342
383
|
.filter(function(value, index, array) { return array.indexOf(value) === index; }),
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
var EventEmitter = require('eventemitter3');
|
|
2
|
+
var cloneDeep = require('lodash.clonedeep');
|
|
3
|
+
var deepFreeze = require('deep-freeze');
|
|
4
|
+
|
|
5
|
+
function withVideoParams(Video) {
|
|
6
|
+
function VideoWithVideoParams(options) {
|
|
7
|
+
options = options || {};
|
|
8
|
+
|
|
9
|
+
var video = new Video(options);
|
|
10
|
+
video.on('propValue', onVideoPropEvent.bind(null, 'propValue'));
|
|
11
|
+
video.on('propChanged', onVideoPropEvent.bind(null, 'propChanged'));
|
|
12
|
+
Video.manifest.events
|
|
13
|
+
.filter(function(eventName) {
|
|
14
|
+
return !['propValue', 'propChanged'].includes(eventName);
|
|
15
|
+
})
|
|
16
|
+
.forEach(function(eventName) {
|
|
17
|
+
video.on(eventName, onOtherVideoEvent(eventName));
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
var stream = null;
|
|
21
|
+
var events = new EventEmitter();
|
|
22
|
+
var destroyed = false;
|
|
23
|
+
var observedProps = {
|
|
24
|
+
videoParams: false
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function onVideoPropEvent(eventName, propName, propValue) {
|
|
28
|
+
if (propName !== 'videoParams') {
|
|
29
|
+
events.emit(eventName, propName, getProp(propName, propValue));
|
|
30
|
+
}
|
|
31
|
+
if (propName === 'stream') {
|
|
32
|
+
stream = propValue;
|
|
33
|
+
onPropChanged('videoParams');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function onOtherVideoEvent(eventName) {
|
|
37
|
+
return function() {
|
|
38
|
+
events.emit.apply(events, [eventName].concat(Array.from(arguments)));
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function onPropChanged(propName) {
|
|
42
|
+
if (observedProps[propName]) {
|
|
43
|
+
events.emit('propChanged', propName, getProp(propName, null));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function getProp(propName, videoPropValue) {
|
|
47
|
+
switch (propName) {
|
|
48
|
+
case 'videoParams': {
|
|
49
|
+
if (stream === null) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
var hash = stream.behaviorHints && typeof stream.behaviorHints.videoHash === 'string' ? stream.behaviorHints.videoHash : null;
|
|
54
|
+
var size = stream.behaviorHints && stream.behaviorHints.videoSize !== null && isFinite(stream.behaviorHints.videoSize) ? stream.behaviorHints.videoSize : null;
|
|
55
|
+
var filename = stream.behaviorHints && typeof stream.behaviorHints.filename === 'string' ? stream.behaviorHints.filename : null;
|
|
56
|
+
return { hash: hash, size: size, filename: filename };
|
|
57
|
+
}
|
|
58
|
+
default: {
|
|
59
|
+
return videoPropValue;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function observeProp(propName) {
|
|
64
|
+
switch (propName) {
|
|
65
|
+
case 'videoParams': {
|
|
66
|
+
events.emit('propValue', propName, getProp(propName, null));
|
|
67
|
+
observedProps[propName] = true;
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
default: {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function command(commandName) {
|
|
76
|
+
switch (commandName) {
|
|
77
|
+
case 'destroy': {
|
|
78
|
+
destroyed = true;
|
|
79
|
+
video.dispatch({ type: 'command', commandName: 'destroy' });
|
|
80
|
+
events.removeAllListeners();
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
default: {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
this.on = function(eventName, listener) {
|
|
90
|
+
if (destroyed) {
|
|
91
|
+
throw new Error('Video is destroyed');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
events.on(eventName, listener);
|
|
95
|
+
};
|
|
96
|
+
this.dispatch = function(action) {
|
|
97
|
+
if (destroyed) {
|
|
98
|
+
throw new Error('Video is destroyed');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (action) {
|
|
102
|
+
action = deepFreeze(cloneDeep(action));
|
|
103
|
+
switch (action.type) {
|
|
104
|
+
case 'observeProp': {
|
|
105
|
+
if (observeProp(action.propName)) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
case 'command': {
|
|
112
|
+
if (command(action.commandName, action.commandArgs)) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
video.dispatch(action);
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
VideoWithVideoParams.canPlayStream = function(stream, options) {
|
|
126
|
+
return Video.canPlayStream(stream, options);
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
VideoWithVideoParams.manifest = {
|
|
130
|
+
name: Video.manifest.name + 'WithVideoParams',
|
|
131
|
+
external: Video.manifest.external,
|
|
132
|
+
props: Video.manifest.props.concat(['videoParams'])
|
|
133
|
+
.filter(function(value, index, array) { return array.indexOf(value) === index; }),
|
|
134
|
+
commands: Video.manifest.commands.concat(['destroy'])
|
|
135
|
+
.filter(function(value, index, array) { return array.indexOf(value) === index; }),
|
|
136
|
+
events: Video.manifest.events.concat(['propValue', 'propChanged'])
|
|
137
|
+
.filter(function(value, index, array) { return array.indexOf(value) === index; })
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
return VideoWithVideoParams;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
module.exports = withVideoParams;
|