@stremio/stremio-video 0.0.8 → 0.0.12
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 +6 -5
- package/src/ChromecastSenderVideo/ChromecastSenderVideo.js +15 -8
- package/src/HTMLVideo/HTMLVideo.js +7 -17
- package/src/HTMLVideo/getContentType.js +16 -0
- package/src/HTMLVideo/hlsConfig.js +15 -0
- package/src/IFrameVideo/IFrameVideo.js +130 -0
- package/src/IFrameVideo/index.js +3 -0
- package/src/StremioVideo/StremioVideo.js +11 -5
- package/src/StremioVideo/selectVideoImplementation.js +36 -0
- package/src/YouTubeVideo/YouTubeVideo.js +12 -9
- package/src/error.js +5 -25
- package/src/index.js +1 -13
- package/src/withHTMLSubtitles/subtitlesParser.js +69 -40
- package/src/withHTMLSubtitles/withHTMLSubtitles.js +59 -50
- package/src/withStreamingServer/convertStream.js +61 -0
- package/src/withStreamingServer/inferTorrentFileIdx.js +63 -0
- package/src/withStreamingServer/withStreamingServer.js +101 -189
- package/src/withHTMLSubtitles/fetchSubtitles.js +0 -27
- package/src/withStreamingServer/convertStreamToURL.js +0 -142
- package/src/withStreamingServer/createTranscoder.js +0 -46
- package/src/withStreamingServer/transcodeNextSegment.js +0 -17
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
var EventEmitter = require('eventemitter3');
|
|
2
2
|
var cloneDeep = require('lodash.clonedeep');
|
|
3
3
|
var deepFreeze = require('deep-freeze');
|
|
4
|
+
var ERROR = require('../error');
|
|
5
|
+
var subtitlesParser = require('./subtitlesParser');
|
|
4
6
|
var subtitlesRenderer = require('./subtitlesRenderer');
|
|
5
|
-
var fetchSubtitles = require('./fetchSubtitles');
|
|
6
7
|
|
|
7
8
|
function withHTMLSubtitles(Video) {
|
|
8
9
|
function VideoWithHTMLSubtitles(options) {
|
|
@@ -14,10 +15,10 @@ function withHTMLSubtitles(Video) {
|
|
|
14
15
|
video.on('propChanged', onVideoPropEvent.bind(null, 'propChanged'));
|
|
15
16
|
Video.manifest.events
|
|
16
17
|
.filter(function(eventName) {
|
|
17
|
-
return !['error', '
|
|
18
|
+
return !['error', 'propValue', 'propChanged'].includes(eventName);
|
|
18
19
|
})
|
|
19
20
|
.forEach(function(eventName) {
|
|
20
|
-
video.on(eventName,
|
|
21
|
+
video.on(eventName, onOtherVideoEvent(eventName));
|
|
21
22
|
});
|
|
22
23
|
|
|
23
24
|
var containerElement = options.containerElement;
|
|
@@ -81,6 +82,40 @@ function withHTMLSubtitles(Video) {
|
|
|
81
82
|
subtitlesElement.append(cueNode, document.createElement('br'));
|
|
82
83
|
});
|
|
83
84
|
}
|
|
85
|
+
function onVideoError(error) {
|
|
86
|
+
events.emit('error', error);
|
|
87
|
+
if (error.critical) {
|
|
88
|
+
command('unload');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function onVideoPropEvent(eventName, propName, propValue) {
|
|
92
|
+
switch (propName) {
|
|
93
|
+
case 'time': {
|
|
94
|
+
videoState.time = propValue;
|
|
95
|
+
renderSubtitles();
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
events.emit(eventName, propName, getProp(propName, propValue));
|
|
101
|
+
}
|
|
102
|
+
function onOtherVideoEvent(eventName) {
|
|
103
|
+
return function() {
|
|
104
|
+
events.emit.apply(events, [eventName].concat(Array.from(arguments)));
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function onPropChanged(propName) {
|
|
108
|
+
if (observedProps[propName]) {
|
|
109
|
+
events.emit('propChanged', propName, getProp(propName, null));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function onError(error) {
|
|
113
|
+
events.emit('error', error);
|
|
114
|
+
if (error.critical) {
|
|
115
|
+
command('unload');
|
|
116
|
+
video.dispatch({ type: 'command', commandName: 'unload' });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
84
119
|
function getProp(propName, videoPropValue) {
|
|
85
120
|
switch (propName) {
|
|
86
121
|
case 'extraSubtitlesTracks': {
|
|
@@ -144,40 +179,6 @@ function withHTMLSubtitles(Video) {
|
|
|
144
179
|
}
|
|
145
180
|
}
|
|
146
181
|
}
|
|
147
|
-
function onError(error) {
|
|
148
|
-
events.emit('error', error);
|
|
149
|
-
if (error.critical) {
|
|
150
|
-
command('unload');
|
|
151
|
-
video.dispatch({ type: 'command', commandName: 'unload' });
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
function onVideoError(error) {
|
|
155
|
-
events.emit('error', error);
|
|
156
|
-
if (error.critical) {
|
|
157
|
-
command('unload');
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
function onVideoPropEvent(eventName, propName, propValue) {
|
|
161
|
-
switch (propName) {
|
|
162
|
-
case 'time': {
|
|
163
|
-
videoState.time = propValue;
|
|
164
|
-
renderSubtitles();
|
|
165
|
-
break;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
events.emit(eventName, propName, getProp(propName, propValue));
|
|
170
|
-
}
|
|
171
|
-
function onVideoOtherEvent(eventName) {
|
|
172
|
-
return function() {
|
|
173
|
-
events.emit.apply(events, [eventName].concat(Array.from(arguments)));
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
function onPropChanged(propName) {
|
|
177
|
-
if (observedProps[propName]) {
|
|
178
|
-
events.emit('propChanged', propName, getProp(propName));
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
182
|
function observeProp(propName) {
|
|
182
183
|
switch (propName) {
|
|
183
184
|
case 'extraSubtitlesTracks':
|
|
@@ -188,7 +189,7 @@ function withHTMLSubtitles(Video) {
|
|
|
188
189
|
case 'extraSubtitlesTextColor':
|
|
189
190
|
case 'extraSubtitlesBackgroundColor':
|
|
190
191
|
case 'extraSubtitlesShadowColor': {
|
|
191
|
-
events.emit('propValue', propName, getProp(propName));
|
|
192
|
+
events.emit('propValue', propName, getProp(propName, null));
|
|
192
193
|
observedProps[propName] = true;
|
|
193
194
|
return true;
|
|
194
195
|
}
|
|
@@ -203,28 +204,36 @@ function withHTMLSubtitles(Video) {
|
|
|
203
204
|
cuesByTime = null;
|
|
204
205
|
selectedTrackId = null;
|
|
205
206
|
delay = null;
|
|
206
|
-
var
|
|
207
|
+
var selectedTrack = tracks.find(function(track) {
|
|
207
208
|
return track.id === propValue;
|
|
208
209
|
});
|
|
209
|
-
if (
|
|
210
|
-
selectedTrackId =
|
|
210
|
+
if (selectedTrack) {
|
|
211
|
+
selectedTrackId = selectedTrack.id;
|
|
211
212
|
delay = 0;
|
|
212
|
-
|
|
213
|
+
fetch(selectedTrack.url)
|
|
213
214
|
.then(function(resp) {
|
|
214
|
-
|
|
215
|
+
return resp.text();
|
|
216
|
+
})
|
|
217
|
+
.then(function(text) {
|
|
218
|
+
return subtitlesParser.parse(text);
|
|
219
|
+
})
|
|
220
|
+
.then(function(result) {
|
|
221
|
+
if (selectedTrackId !== selectedTrack.id) {
|
|
215
222
|
return;
|
|
216
223
|
}
|
|
217
224
|
|
|
218
|
-
cuesByTime =
|
|
225
|
+
cuesByTime = result;
|
|
219
226
|
renderSubtitles();
|
|
220
|
-
events.emit('extraSubtitlesTrackLoaded',
|
|
227
|
+
events.emit('extraSubtitlesTrackLoaded', selectedTrack);
|
|
221
228
|
})
|
|
222
229
|
.catch(function(error) {
|
|
223
|
-
if (selectedTrackId !==
|
|
230
|
+
if (selectedTrackId !== selectedTrack.id) {
|
|
224
231
|
return;
|
|
225
232
|
}
|
|
226
233
|
|
|
227
|
-
onError(Object.assign({},
|
|
234
|
+
onError(Object.assign({}, ERROR.WITH_HTML_SUBTITLES.LOAD_FAILED, {
|
|
235
|
+
error: error,
|
|
236
|
+
track: selectedTrack,
|
|
228
237
|
critical: false
|
|
229
238
|
}));
|
|
230
239
|
});
|
|
@@ -318,8 +327,8 @@ function withHTMLSubtitles(Video) {
|
|
|
318
327
|
command('unload');
|
|
319
328
|
if (commandArgs.stream && Array.isArray(commandArgs.stream.subtitles)) {
|
|
320
329
|
command('addExtraSubtitlesTracks', {
|
|
321
|
-
tracks: commandArgs.stream.subtitles.map(function(
|
|
322
|
-
return Object.assign({},
|
|
330
|
+
tracks: commandArgs.stream.subtitles.map(function(track) {
|
|
331
|
+
return Object.assign({}, track, {
|
|
323
332
|
origin: 'EXCLUSIVE',
|
|
324
333
|
exclusive: true,
|
|
325
334
|
embedded: false
|
|
@@ -414,7 +423,7 @@ function withHTMLSubtitles(Video) {
|
|
|
414
423
|
.filter(function(value, index, array) { return array.indexOf(value) === index; }),
|
|
415
424
|
commands: Video.manifest.commands.concat(['load', 'unload', 'destroy', 'addExtraSubtitlesTracks'])
|
|
416
425
|
.filter(function(value, index, array) { return array.indexOf(value) === index; }),
|
|
417
|
-
events: Video.manifest.events.concat(['
|
|
426
|
+
events: Video.manifest.events.concat(['propValue', 'propChanged', 'error', 'extraSubtitlesTrackLoaded'])
|
|
418
427
|
.filter(function(value, index, array) { return array.indexOf(value) === index; })
|
|
419
428
|
};
|
|
420
429
|
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
var url = require('url');
|
|
2
|
+
var magnet = require('magnet-uri');
|
|
3
|
+
var inferTorrentFileIdx = require('./inferTorrentFileIdx');
|
|
4
|
+
|
|
5
|
+
function convertStream(streamingServerURL, stream, seriesInfo) {
|
|
6
|
+
return new Promise(function(resolve, reject) {
|
|
7
|
+
if (typeof stream.url === 'string') {
|
|
8
|
+
if (stream.url.indexOf('magnet:') === 0) {
|
|
9
|
+
var parsedMagnetURI;
|
|
10
|
+
try {
|
|
11
|
+
parsedMagnetURI = magnet.decode(stream.url);
|
|
12
|
+
if (!parsedMagnetURI || typeof parsedMagnetURI.infoHash !== 'string') {
|
|
13
|
+
reject(new Error('Failed to decode magnet url'));
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
} catch (error) {
|
|
17
|
+
reject(error);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
var sources = Array.isArray(parsedMagnetURI.announce) ?
|
|
22
|
+
parsedMagnetURI.announce.map(function(source) {
|
|
23
|
+
return 'tracker:' + source;
|
|
24
|
+
})
|
|
25
|
+
:
|
|
26
|
+
[];
|
|
27
|
+
inferTorrentFileIdx(streamingServerURL, parsedMagnetURI.infoHash, sources, seriesInfo)
|
|
28
|
+
.then(function(fileIdx) {
|
|
29
|
+
resolve(url.resolve(streamingServerURL, '/' + encodeURIComponent(parsedMagnetURI.infoHash) + '/' + encodeURIComponent(fileIdx)));
|
|
30
|
+
})
|
|
31
|
+
.catch(function(error) {
|
|
32
|
+
reject(error);
|
|
33
|
+
});
|
|
34
|
+
} else {
|
|
35
|
+
resolve(stream.url);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (typeof stream.infoHash === 'string') {
|
|
42
|
+
if (stream.fileIdx !== null && isFinite(stream.fileIdx)) {
|
|
43
|
+
resolve(url.resolve(streamingServerURL, '/' + encodeURIComponent(stream.infoHash) + '/' + encodeURIComponent(stream.fileIdx)));
|
|
44
|
+
} else {
|
|
45
|
+
inferTorrentFileIdx(streamingServerURL, stream.infoHash, stream.announce, seriesInfo)
|
|
46
|
+
.then(function(fileIdx) {
|
|
47
|
+
resolve(url.resolve(streamingServerURL, '/' + encodeURIComponent(stream.infoHash) + '/' + encodeURIComponent(fileIdx)));
|
|
48
|
+
})
|
|
49
|
+
.catch(function(error) {
|
|
50
|
+
reject(error);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
reject(new Error('Stream cannot be converted'));
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
module.exports = convertStream;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
var url = require('url');
|
|
2
|
+
var parseVideoName = require('video-name-parser');
|
|
3
|
+
|
|
4
|
+
var MEDIA_FILE_EXTENTIONS = /.mkv$|.avi$|.mp4$|.wmv$|.vp8$|.mov$|.mpg$|.ts$|.m3u8$|.webm$|.flac$|.mp3$|.wav$|.wma$|.aac$|.ogg$/i;
|
|
5
|
+
|
|
6
|
+
function inferTorrentFileIdx(streamingServerURL, infoHash, sources, seriesInfo) {
|
|
7
|
+
return fetch(url.resolve(streamingServerURL, '/' + encodeURIComponent(infoHash) + '/create'), {
|
|
8
|
+
method: 'POST',
|
|
9
|
+
headers: {
|
|
10
|
+
'content-type': 'application/json'
|
|
11
|
+
},
|
|
12
|
+
body: JSON.stringify({
|
|
13
|
+
torrent: {
|
|
14
|
+
infoHash: infoHash,
|
|
15
|
+
peerSearch: {
|
|
16
|
+
sources: ['dht:' + infoHash].concat(Array.isArray(sources) ? sources : []),
|
|
17
|
+
min: 40,
|
|
18
|
+
max: 150
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
}).then(function(resp) {
|
|
23
|
+
return resp.json();
|
|
24
|
+
}).then(function(resp) {
|
|
25
|
+
if (!resp || !Array.isArray(resp.files) || resp.files.some(function(file) { return !file || typeof file.path !== 'string' || file.length === null || !isFinite(file.length); })) {
|
|
26
|
+
throw new Error('No files found in the torrent');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
var mediaFiles = resp.files.filter(function(file) {
|
|
30
|
+
return file.path.match(MEDIA_FILE_EXTENTIONS);
|
|
31
|
+
});
|
|
32
|
+
if (mediaFiles.length === 0) {
|
|
33
|
+
throw new Error('No media files found in the torrent');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
var mediaFilesForEpisode = seriesInfo ?
|
|
37
|
+
mediaFiles.filter(function(file) {
|
|
38
|
+
try {
|
|
39
|
+
var info = parseVideoName(file.path);
|
|
40
|
+
return info.season !== null &&
|
|
41
|
+
isFinite(info.season) &&
|
|
42
|
+
info.season === seriesInfo.season &&
|
|
43
|
+
Array.isArray(info.episode) &&
|
|
44
|
+
info.episode.indexOf(seriesInfo.episode) !== -1;
|
|
45
|
+
} catch (e) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
:
|
|
50
|
+
[];
|
|
51
|
+
var selectedFile = (mediaFilesForEpisode.length > 0 ? mediaFilesForEpisode : mediaFiles)
|
|
52
|
+
.reduce(function(result, file) {
|
|
53
|
+
if (!result || file.length > result.length) {
|
|
54
|
+
return file;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return result;
|
|
58
|
+
}, null);
|
|
59
|
+
return resp.files.indexOf(selectedFile);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
module.exports = inferTorrentFileIdx;
|