@stremio/stremio-video 0.0.23 → 0.0.25-rc.1

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stremio/stremio-video",
3
- "version": "0.0.23",
3
+ "version": "0.0.25-rc.1",
4
4
  "description": "Abstraction layer on top of different media players",
5
5
  "author": "Smart Code OOD",
6
6
  "main": "src/index.js",
@@ -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;
@@ -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();
@@ -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(url) {
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(url) {
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 buildTorrentUrl(streamingServerURL, infoHash, fileIdx, sources) {
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 url.resolve(streamingServerURL, '/' + encodeURIComponent(infoHash) + '/' + encodeURIComponent(fileIdx)) + query;
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(buildTorrentUrl(streamingServerURL, infoHash, fileIdx, sources));
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 buildTorrentUrl(streamingServerURL, infoHash, body.guessFileIdx ? resp.guessedFileIdx : fileIdx, body.peerSearch ? body.peerSearch.sources : []);
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;
@@ -3,7 +3,9 @@ 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');
8
+ var fetchVideoParams = require('./fetchVideoParams');
7
9
  var ERROR = require('../error');
8
10
 
9
11
  function withStreamingServer(Video) {
@@ -26,10 +28,12 @@ function withStreamingServer(Video) {
26
28
  var loadArgs = null;
27
29
  var loaded = false;
28
30
  var actionsQueue = [];
31
+ var videoParams = null;
29
32
  var events = new EventEmitter();
30
33
  var destroyed = false;
31
34
  var observedProps = {
32
- stream: false
35
+ stream: false,
36
+ videoParams: false
33
37
  };
34
38
 
35
39
  function flushActionsQueue() {
@@ -69,6 +73,9 @@ function withStreamingServer(Video) {
69
73
  case 'stream': {
70
74
  return loadArgs !== null ? loadArgs.stream : null;
71
75
  }
76
+ case 'videoParams': {
77
+ return videoParams;
78
+ }
72
79
  default: {
73
80
  return videoPropValue;
74
81
  }
@@ -76,7 +83,8 @@ function withStreamingServer(Video) {
76
83
  }
77
84
  function observeProp(propName) {
78
85
  switch (propName) {
79
- case 'stream': {
86
+ case 'stream':
87
+ case 'videoParams': {
80
88
  events.emit('propValue', propName, getProp(propName, null));
81
89
  observedProps[propName] = true;
82
90
  return true;
@@ -95,8 +103,33 @@ function withStreamingServer(Video) {
95
103
  loadArgs = commandArgs;
96
104
  onPropChanged('stream');
97
105
  convertStream(commandArgs.streamingServerURL, commandArgs.stream, commandArgs.seriesInfo)
98
- .then(function(mediaURL) {
99
- return (commandArgs.forceTranscoding ? Promise.resolve(false) : Video.canPlayStream({ url: mediaURL }))
106
+ .then(function(result) {
107
+ var mediaURL = result.url;
108
+ var infoHash = result.infoHash;
109
+ var fileIdx = result.fileIdx;
110
+ var formats = Array.isArray(commandArgs.formats) ?
111
+ commandArgs.formats
112
+ :
113
+ mediaCapabilities.formats;
114
+ var videoCodecs = Array.isArray(commandArgs.videoCodecs) ?
115
+ commandArgs.videoCodecs
116
+ :
117
+ mediaCapabilities.videoCodecs;
118
+ var audioCodecs = Array.isArray(commandArgs.audioCodecs) ?
119
+ commandArgs.audioCodecs
120
+ :
121
+ mediaCapabilities.audioCodecs;
122
+ var maxAudioChannels = commandArgs.maxAudioChannels !== null && isFinite(commandArgs.maxAudioChannels) ?
123
+ commandArgs.maxAudioChannels
124
+ :
125
+ mediaCapabilities.maxAudioChannels;
126
+ var canPlayStreamOptions = Object.assign({}, commandArgs, {
127
+ formats: formats,
128
+ videoCodecs: videoCodecs,
129
+ audioCodecs: audioCodecs,
130
+ maxAudioChannels: maxAudioChannels
131
+ });
132
+ return (commandArgs.forceTranscoding ? Promise.resolve(false) : VideoWithStreamingServer.canPlayStream({ url: mediaURL }, canPlayStreamOptions))
100
133
  .catch(function(error) {
101
134
  console.warn('Media probe error', error);
102
135
  return false;
@@ -104,7 +137,12 @@ function withStreamingServer(Video) {
104
137
  .then(function(canPlay) {
105
138
  if (canPlay) {
106
139
  return {
107
- url: mediaURL
140
+ mediaURL: mediaURL,
141
+ infoHash: infoHash,
142
+ fileIdx: fileIdx,
143
+ stream: {
144
+ url: mediaURL
145
+ }
108
146
  };
109
147
  }
110
148
 
@@ -113,32 +151,44 @@ function withStreamingServer(Video) {
113
151
  if (commandArgs.forceTranscoding) {
114
152
  queryParams.set('forceTranscoding', '1');
115
153
  }
116
- if (commandArgs.maxAudioChannels !== null && isFinite(commandArgs.maxAudioChannels)) {
117
- queryParams.set('maxAudioChannels', commandArgs.maxAudioChannels);
118
- }
154
+
155
+ videoCodecs.forEach(function(videoCodec) {
156
+ queryParams.append('videoCodecs', videoCodec);
157
+ });
158
+
159
+ audioCodecs.forEach(function(audioCodec) {
160
+ queryParams.append('audioCodecs', audioCodec);
161
+ });
162
+
163
+ queryParams.set('maxAudioChannels', maxAudioChannels);
119
164
 
120
165
  return {
121
- url: url.resolve(commandArgs.streamingServerURL, '/hlsv2/' + id + '/master.m3u8?' + queryParams.toString()),
122
- subtitles: Array.isArray(commandArgs.stream.subtitles) ?
123
- commandArgs.stream.subtitles.map(function(track) {
124
- return Object.assign({}, track, {
125
- url: typeof track.url === 'string' ?
126
- url.resolve(commandArgs.streamingServerURL, '/subtitles.vtt?' + new URLSearchParams([['from', track.url]]).toString())
127
- :
128
- track.url
129
- });
130
- })
131
- :
132
- [],
133
- behaviorHints: {
134
- headers: {
135
- 'content-type': 'application/vnd.apple.mpegurl'
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
+ }
136
186
  }
137
187
  }
138
188
  };
139
189
  });
140
190
  })
141
- .then(function(stream) {
191
+ .then(function(result) {
142
192
  if (commandArgs !== loadArgs) {
143
193
  return;
144
194
  }
@@ -147,11 +197,30 @@ function withStreamingServer(Video) {
147
197
  type: 'command',
148
198
  commandName: 'load',
149
199
  commandArgs: Object.assign({}, commandArgs, {
150
- stream: stream
200
+ stream: result.stream
151
201
  })
152
202
  });
153
203
  loaded = true;
154
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
+ });
155
224
  })
156
225
  .catch(function(error) {
157
226
  if (commandArgs !== loadArgs) {
@@ -207,7 +276,9 @@ function withStreamingServer(Video) {
207
276
  loadArgs = null;
208
277
  loaded = false;
209
278
  actionsQueue = [];
279
+ videoParams = null;
210
280
  onPropChanged('stream');
281
+ onPropChanged('videoParams');
211
282
  return false;
212
283
  }
213
284
  case 'destroy': {
@@ -269,14 +340,44 @@ function withStreamingServer(Video) {
269
340
  };
270
341
  }
271
342
 
272
- VideoWithStreamingServer.canPlayStream = function(stream) {
273
- return Video.canPlayStream(stream);
343
+ VideoWithStreamingServer.canPlayStream = function(stream, options) {
344
+ return Video.canPlayStream(stream)
345
+ .then(function(canPlay) {
346
+ if (!canPlay) {
347
+ throw new Error('Fallback using /hlsv2/probe');
348
+ }
349
+
350
+ return canPlay;
351
+ })
352
+ .catch(function() {
353
+ var queryParams = new URLSearchParams([['mediaURL', stream.url]]);
354
+ return fetch(url.resolve(options.streamingServerURL, '/hlsv2/probe?' + queryParams.toString()))
355
+ .then(function(resp) {
356
+ return resp.json();
357
+ })
358
+ .then(function(probe) {
359
+ var isFormatSupported = options.formats.some(function(format) {
360
+ return probe.format.name.indexOf(format) !== -1;
361
+ });
362
+ var areStreamsSupported = probe.streams.every(function(stream) {
363
+ if (stream.track === 'audio') {
364
+ return stream.channels <= options.maxAudioChannels &&
365
+ options.audioCodecs.indexOf(stream.codec) !== -1;
366
+ } else if (stream.track === 'video') {
367
+ return options.videoCodecs.indexOf(stream.codec) !== -1;
368
+ }
369
+
370
+ return true;
371
+ });
372
+ return isFormatSupported && areStreamsSupported;
373
+ });
374
+ });
274
375
  };
275
376
 
276
377
  VideoWithStreamingServer.manifest = {
277
378
  name: Video.manifest.name + 'WithStreamingServer',
278
379
  external: Video.manifest.external,
279
- props: Video.manifest.props.concat(['stream'])
380
+ props: Video.manifest.props.concat(['stream', 'videoParams'])
280
381
  .filter(function(value, index, array) { return array.indexOf(value) === index; }),
281
382
  commands: Video.manifest.commands.concat(['load', 'unload', 'destroy', 'addExtraSubtitlesTracks'])
282
383
  .filter(function(value, index, array) { return array.indexOf(value) === index; }),
@@ -0,0 +1,3 @@
1
+ var withVideoParams = require('./withVideoParams');
2
+
3
+ module.exports = withVideoParams;
@@ -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;