@vidispine/vdt-videojs 23.1.0 → 23.2.0-pre.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.
@@ -1,614 +0,0 @@
1
- /* eslint-disable no-console */
2
- import 'video.js/dist/video-js.css';
3
- import '../dist/index.css';
4
-
5
- // example import for pure js use
6
- import pkgJson from '../package.json'; // for version codes and other useful info
7
- import { Player, ExternalControls, SeekBar, TimeCodeDisplay } from '../src/index';
8
-
9
- import subtitleTestFile2 from './samples/birds-es.vvt';
10
- import subtitleTestFile from './samples/birds.vvt';
11
- // simple smokescreen tests
12
- import runTests, { log } from './tests';
13
-
14
- // subtitles - imports return file urls (hased file output in parcel packager)
15
-
16
- const urlParams = new URLSearchParams(window.location.search);
17
- const liveDemoMode = urlParams.get('live_demo') === 'true';
18
-
19
- const bodyClassName = [liveDemoMode ? 'live-demo' : ''].join(' ');
20
- if (bodyClassName.length > 0) document.getElementsByTagName('body')[0].className += bodyClassName;
21
-
22
- document.title = `${pkgJson.name} - ${pkgJson.version}`;
23
-
24
- // will return random int from min / max
25
- function getIntInRange(min, max) {
26
- return Math.floor(Math.random() * (max - min + 1)) + min;
27
- }
28
-
29
- // outputs an html label for demo needs
30
- function makeLabel(title) {
31
- const label = document.createElement('label');
32
- label.className = `vdt-label ${title.toLowerCase().replace('.', '-')}`;
33
- label.textContent = title;
34
- return label;
35
- }
36
-
37
- // outputs a segmented button box
38
- function makeButtonBox(buttons) {
39
- const buttonBox = document.createElement('table');
40
- buttonBox.innerHTML = '<tr></tr>';
41
- buttonBox.className = 'component-api-test-buttons';
42
- buttons.forEach((button) => {
43
- const buttonEl = document.createElement('td');
44
- buttonEl.textContent = button.title;
45
- buttonEl.onclick = button.method;
46
- buttonBox.getElementsByTagName('tr')[0].appendChild(buttonEl);
47
- });
48
-
49
- return buttonBox;
50
- }
51
-
52
- // test data
53
- const testData = [
54
- // bunch of videos for variety, different aspect ratios and bitrates
55
- {
56
- sources: [{ src: '//vjs.zencdn.net/v/oceans.mp4', type: 'video/mp4', label: 'Birds video' }],
57
- posterUrl:
58
- 'https://www.nationalgeographic.com/content/dam/news/2016/11/09/birds-plastic/birds-plastic.jpg',
59
- subtitles: [
60
- {
61
- src: subtitleTestFile,
62
- lang: 'en',
63
- label: 'English',
64
- },
65
- {
66
- src: subtitleTestFile2,
67
- lang: 'es',
68
- label: 'Spanish',
69
- },
70
- {
71
- src: new Blob(
72
- [
73
- 'WEBVTT\n\n\n00:00:00.500 --> 00:00:03.500 line:0%\nFuglene...\n\n00:00:03.800 --> 00:00:05.800 line:0%\nDe flyver og dykker i havet.\n\n00:00:06.000 --> 00:00:07.000 line:0%\nOg nu...\n\n00:00:07.200 --> 00:00:08.000 position:58% line:0%\nFuglene fester.\n\n00:00:08.200 --> 00:00:10.000 line:0%\nYumm, elskede fisk!\n\n00:00:10.100 --> 00:00:12.000 line:0%\nFiskene har masser af næringsstoffer i sig.\n\n00:00:12.000 --> 00:00:13.200 line:0%\nHvad hvis fisken kan føle smerter?\n\n00:00:13.200 --> 00:00:14.500 position:61% line:0%\nDet er en tanke.\n\n00:00:14.522 --> 00:00:16.900 line:0%\nTaler fisk engelsk?\n\n00:00:16.950 --> 00:00:18.200 line:0%\nGulp gulp gl gllllp ...\n\n00:00:18.300 --> 00:00:20.200 line:0%\nVi er nødt til at stoppe den useriøse fisk.\n\n00:00:20.333 --> 00:00:25.333 line:0%\nSwoosh swooosh; dykke eller dø\n\n00:00:26.000 --> 00:00:28.240 line:0%\nDet ser sammen.\n\n00:00:28.951 --> 00:00:30.785 line:0%\nHar du !!!\n\n00:00:30.900 --> 00:00:32.000 line:0%\nO-åh, de store fisker kommer.\n\n00:00:33.200 --> 00:00:36.044 line:0%\n"Vi vil afslutte dig"\n\n00:00:36.800 --> 00:00:42.547 line:0%\nOK, jeg skal hoppe, HVALE dette har ikke gået dårligere end det er; Tjek denne lange undertekstlinie!',
74
- ],
75
- { type: 'text/plain' },
76
- ),
77
- lang: 'dk',
78
- label: 'Danish',
79
- },
80
- ],
81
- frameRate: 24000 / 1001,
82
- },
83
- /*
84
- { // has multiple in-band audio tracks
85
- sources: [
86
- { src: 'https://demo-myvidispine-com-media.s3-eu-west-1.amazonaws.com/HDALVN401A.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20200311T095703Z&X-Amz-SignedHeaders=host&X-Amz-Expires=39599&X-Amz-Credential=AKIAJJLDPJZTNRHQLXJQ%2F20200311%2Feu-west-1%2Fs3%2Faws4_request&X-Amz-Signature=0399b32a0af123ded7f02d1b37c7ee5ef116105774c1e7a6cbde0d49bb2a3760', type: 'video/mp4' }
87
- ]
88
- },
89
- {
90
- sources: [{
91
- src: '//commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',
92
- type: 'video/mp4'
93
- }],
94
- posterUrl: 'https://new.static.tv.nu/31999293?forceFit=0&height=760&quality=50&width=1350'
95
- },
96
- {
97
- sources: [{
98
- src: '//commondatastorage.googleapis.com/gtv-videos-bucket/sample/TearsOfSteel.mp4',
99
- type: 'video/mp4'
100
- }],
101
- posterUrl: 'https://mango.blender.org/wp-content/uploads/2013/05/01_thom_celia_bridge.jpg?x52477'
102
- },
103
- {
104
- sources: [{
105
- src:'//commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4',
106
- type: 'video/mp4'
107
- }],
108
- posterUrl: 'https://upload.wikimedia.org/wikipedia/commons/e/e8/Elephants_Dream_s5_both.jpg'
109
- },
110
- {
111
- sources: [{
112
- src: '//commondatastorage.googleapis.com/gtv-videos-bucket/sample/Sintel.mp4',
113
- type: 'video/mp4'
114
- }],
115
- posterUrl: 'https://ageekandthesea.files.wordpress.com/2016/10/i_love_sintel_by_nash88-d2zkxw5.jpg'
116
- },
117
-
118
- // timecode samples
119
- {
120
- sources: [{
121
- src: 'https://www.mediacollege.com/downloads/video/timecode/tc_29.97fps_df_1min.mov',
122
- type: 'video/mp4',
123
- }],
124
- frameRate: (30000 / 1001),
125
- },
126
- // audio only
127
- {
128
- sources: [{
129
- src: 'https://files.freemusicarchive.org/storage-freemusicarchive-org/music/ccCommunity/Dee_Yan-Key/Classical_Lounge/Dee_Yan-Key_-_06_-_Jauntiness.mp3',
130
- type: 'audio/mp3'
131
- }],
132
- posterUrl: 'https://pbs.twimg.com/media/Dp4ShWjX4AAwsyW.jpg'
133
- }
134
- */
135
- ];
136
-
137
- // test the dark theme capabilities of components
138
- let onDark = true;
139
- function setupLightSwitch() {
140
- function applyBg() {
141
- document.getElementsByTagName('html')[0].className = onDark ? 'dark' : 'light';
142
- }
143
-
144
- function lightSwitch() {
145
- onDark = !onDark;
146
-
147
- applyBg();
148
- // eslint-disable-next-line no-use-before-define
149
- player.onDark = onDark;
150
- // eslint-disable-next-line no-undef
151
- controls.onDark = onDark;
152
- // eslint-disable-next-line no-use-before-define
153
- seekbar.onDark = onDark;
154
- }
155
-
156
- applyBg();
157
-
158
- const logoSwitch = document.createElement('div');
159
- logoSwitch.className = 'vdt-lightswitch';
160
- logoSwitch.onclick = lightSwitch;
161
-
162
- // eslint-disable-next-line no-use-before-define
163
- target.appendChild(logoSwitch);
164
- }
165
-
166
- // example player usage (pure js case)
167
- const target = document.getElementById('demo-content'); // mount-point for component demos
168
- const randIndex = getIntInRange(0, testData.length - 1);
169
-
170
- setupLightSwitch();
171
-
172
- const menuItems = [
173
- {
174
- title: 'Example 1',
175
- getActiveIndex: (subitems) => {
176
- // predicate method for determining active index
177
- let activeIndex = 0;
178
-
179
- subitems.forEach((item, index) => {
180
- // eslint-disable-next-line no-use-before-define
181
- if (item.src === player.src) {
182
- activeIndex = index;
183
- }
184
- });
185
-
186
- return activeIndex;
187
- },
188
- items: [
189
- {
190
- title: 'Option 1',
191
- src: '//vjs.zencdn.net/v/oceans.mp4',
192
- onClick: (event, item) => {
193
- console.log(event, item);
194
- },
195
- },
196
- {
197
- title: 'Option 2',
198
- src: '//commondatastorage.googleapis.com/gtv-videos-bucket/sample/TearsOfSteel.mp4',
199
- someKey: 'value', // store arbitrary data
200
- onClick: (event, item) => {
201
- console.log(event, item);
202
- },
203
- },
204
- ],
205
- },
206
- ];
207
-
208
- // eslint-disable-next-line no-multi-assign
209
- const player = (window.player = new Player({
210
- target,
211
- sources: [
212
- ...testData[randIndex].sources,
213
- {
214
- src: '//commondatastorage.googleapis.com/gtv-videos-bucket/sample/TearsOfSteel.mp4',
215
- type: 'video/mp4',
216
- label: 'Blender video',
217
- },
218
- ],
219
- posterUrl: testData[randIndex].posterUrl, // optional
220
- subtitles: testData[randIndex].subtitles, // array of external subtitle URLs
221
- frameRate: testData[randIndex].frameRate,
222
- controls: true, // optional - use false to disable in-frame controls for the player
223
- controlsBelowPlayer: true,
224
- onDark,
225
- timecode: true, // optional, default -> false
226
- // color: '#e84393', // optional, default -> YT red
227
- onReady: () => {
228
- // called after player has been initialized
229
- console.log('player ready!');
230
- },
231
- onScreenshot: (currentTime) => {
232
- // optional - will show a screenshot button in control bar
233
- console.log(currentTime);
234
- },
235
- // additional menu items
236
- menuItems,
237
- seekBar: {
238
- onRegionClick: (region) => {
239
- console.log(region, 'clicked on a region');
240
- },
241
- onIn: (seconds) => {
242
- console.log(`In point was set - ${seconds}`);
243
- },
244
- onOut: (seconds) => {
245
- console.log(`Out point was set - ${seconds}`);
246
- },
247
- },
248
- }));
249
- target.appendChild(makeLabel('player.js'));
250
-
251
- const playerApiTestButtons = [
252
- {
253
- title: 'mv ctrls',
254
- method: () => {
255
- player.controlsBelowPlayer = !player.controlsBelowPlayer;
256
- },
257
- },
258
- {
259
- title: 'hide ctrls',
260
- method: () => {
261
- player.toggleControls();
262
- },
263
- },
264
- {
265
- title: 'help',
266
- method: () => {
267
- player.help.toggle();
268
- },
269
- },
270
- {
271
- title: 'fs',
272
- method: () => {
273
- player.toggleFullscreen();
274
- },
275
- },
276
- {
277
- title: 'replay',
278
- method: () => {
279
- player.replay();
280
- },
281
- },
282
- {
283
- title: 'subs',
284
- method: () => {
285
- player.toggleSubs();
286
- },
287
- },
288
- {
289
- title: 'timecode',
290
- method: () => {
291
- console.log(player.timeCode.smpte);
292
- log.final(`Current timecode - ${player.timeCode.smpteString}`);
293
- },
294
- },
295
- {
296
- title: 'Vol lvl',
297
- method: () => {
298
- log.final(`${player.volume}`);
299
- },
300
- },
301
- {
302
- title: 'menu',
303
- method: () => {
304
- player.menu.toggle();
305
- },
306
- },
307
- {
308
- title: 'in',
309
- method: () => {
310
- player.seekBar.setInFromCurrent();
311
- },
312
- },
313
- {
314
- title: 'out',
315
- method: () => {
316
- player.seekBar.setOutFromCurrent();
317
- },
318
- },
319
- {
320
- title: 'clr io',
321
- method: () => {
322
- player.seekBar.clearMarkers();
323
- },
324
- },
325
- ];
326
- target.appendChild(makeButtonBox(playerApiTestButtons));
327
-
328
- // seekbar
329
- // eslint-disable-next-line no-multi-assign
330
- const seekbar = (window.seekbar = new SeekBar({
331
- target,
332
- player,
333
- onRegionClick: (region) => {
334
- console.log(region, 'clicked on a region');
335
- },
336
- onDark,
337
- // in: 13.00,
338
- // out: 33.00,
339
- }));
340
- target.appendChild(makeLabel('seekBar.js'));
341
-
342
- // buttons for various seekbar api tests
343
- // target.appendChild(document.createElement('hr'));
344
- const seekbarApiTestButtons = [
345
- {
346
- title: 'Set In',
347
- method: () => {
348
- seekbar.setInFromCurrent();
349
- },
350
- },
351
- {
352
- title: 'Set Out',
353
- method: () => {
354
- seekbar.setOutFromCurrent();
355
- },
356
- },
357
- {
358
- title: 'Get In&Out',
359
- method: () => {
360
- // eslint-disable-next-line no-alert
361
- alert(`In point is set at ${seekbar.in} seconds\nOut point is set at ${seekbar.out} seconds`);
362
- },
363
- },
364
- {
365
- title: 'Go to In',
366
- method: () => {
367
- seekbar.navigateToIn();
368
- },
369
- },
370
- {
371
- title: 'Go to Out',
372
- method: () => {
373
- seekbar.navigateToOut();
374
- },
375
- },
376
- {
377
- title: 'Clear',
378
- method: () => {
379
- seekbar.clearMarkers();
380
- },
381
- },
382
- {
383
- title: 'Regions',
384
- method: () => {
385
- seekbar.regions = [
386
- new SeekBar.Region({
387
- in: 9.7597499,
388
- out: 14,
389
- title: 'Birds',
390
- annotation:
391
- 'Birds are a group of feathered theropod dinosaurs, and constitute the only living dinosaurs.',
392
- }),
393
- new SeekBar.Region({
394
- in: 1,
395
- out: 2.8,
396
- title: 'Cool!',
397
- annotation: 'Lorem ipsum sit dolor atmet my friend...',
398
- }),
399
- new SeekBar.Region({
400
- in: 28,
401
- out: 29,
402
- title: 'Dinosaurs',
403
- annotation:
404
- 'Based on fossil and biological evidence, most scientists accept that birds are a specialised subgroup of theropod dinosaurs.',
405
- }),
406
- new SeekBar.Region({
407
- in: 40,
408
- out: 43,
409
- title: 'Right aligned',
410
- annotation: 'This annotation is right aligned with end of region for fitment. ',
411
- }),
412
- ];
413
- },
414
- },
415
- {
416
- title: 'Log state',
417
- method: () => {
418
- console.log(seekbar.state);
419
- return false;
420
- },
421
- },
422
- ];
423
- target.appendChild(makeButtonBox(seekbarApiTestButtons));
424
-
425
- // external controls
426
- window.controls = new ExternalControls({
427
- target, // mount node
428
- player, // player instance
429
- onDark,
430
- });
431
- target.appendChild(makeLabel('externalControls.js'));
432
-
433
- // timecode
434
- window.timecode = new TimeCodeDisplay({
435
- target,
436
- player,
437
- onInput: true,
438
- });
439
- target.appendChild(makeLabel('timeCodeDisplay.js'));
440
- player.setupTimeCode(window.timecode);
441
-
442
- // description
443
- const toDoTarget = document.getElementById('demo-todos');
444
-
445
- // quick n dirty progress track + goals
446
- const toDoos = [
447
- ['Audio channel selection', 0],
448
- ['Thumbnails while hovering over playback progressbar', 0],
449
- ['Audio only playback - waveform visualization', 0],
450
- ['Refitted videojs built-in SeekBar with the custom SeekBar component', 1],
451
- ['Custom in-frame slideout menu with parametric menu items, toolbar button', 1],
452
- ['Video source switching', 1],
453
- ['External SeekBar - hi speed scrobbing', 1],
454
- ['Option for dark / light theme for out-of-frame controls', 1],
455
- ['Option to use the built-in controls below frame', 1],
456
- ['Regions for custom SeekBar component (seekBar.js)', 1],
457
- ['External TimeCodeDisplay support for player', 1],
458
- ['In / Out markers for seek-bar', 1],
459
- ['External seek-bar component (and react.js wrapper)', 1],
460
- ['External controls component (start, end, play/pause, nextframe, prevframe)', 1],
461
- ['Overlay with keyboard controls (help)', 1],
462
- ['Button for callback with currentTime (for starting thumbnail job)', 1],
463
- ['Accept a string as subtitles file (VVT)', 1],
464
- ['Display a read-only timecode for duration', 1],
465
- ['Timecode input feature', 1],
466
- ['React js wrapper', 1],
467
- ['Custom timecode implementation', 1],
468
- ['Ability to jump by frame forward/backward via < & > keys', 1],
469
- ['Subtitle support - using videojs capabilities', 1],
470
- ['Persistance for settings (like volume / mute for starters)', 1],
471
- ['Visual feedback gizmo on events, i.e. play/pause/jump/volchange like YT osd', 1],
472
- ['Color customization - seek bar, ...', 1],
473
- ['Sort mp4 at the top of the source list, because of an ancient iPad bug', 1],
474
- ['Up / Down arrows should adjust volume', 1],
475
- ['Mute toggle via "M" key', 1],
476
- ['Fix video.js initial play button ugliness', 1],
477
- ['Gradient underneath controls (like YT)', 1],
478
- ['Develop similar color scheme / layout / iconography to YT player', 1],
479
- ['Hotkeys for jumping 10 seconds forward/backward - shuttle controls JKL', 1],
480
- ['Play/Pause with "Space" or "K" keypress (dodge video.js Button issues)', 1],
481
- ['Remove unecessary elements that video.js enforces', 1],
482
- ];
483
-
484
- const description = document.createElement('p');
485
- description.innerHTML = `<a href="https://www.npmjs.com/package/@vidispine/vdt-videojs">
486
- @vidispine/vdt-videojs</a> hosts a customized <a href="https://docs.videojs.com/">video.js</a>
487
- player with broad format capabilities, built-in subtitle / caption support, scrubbing
488
- and other features.
489
-
490
- It sets out to mitigate several UI/UX caveats; <br />
491
- <br />Here's how we're progressing at improvements ( ${toDoos.length} additions ):`;
492
-
493
- toDoTarget.appendChild(description);
494
-
495
- const progressBar = document.createElement('div');
496
- progressBar.className = 'progress-bar';
497
- const percent = (toDoos.filter((todo) => todo[1]).length / toDoos.length) * 100;
498
- progressBar.innerHTML = `<div class="fill" style="width: ${percent}%">${percent.toFixed(
499
- 2,
500
- )} %</div>`;
501
- toDoTarget.appendChild(progressBar);
502
-
503
- toDoos.forEach((todo, index) => {
504
- const checkItem = document.createElement('div');
505
- checkItem.innerHTML = `
506
- <span class="to-do"><input type="checkbox" onclick="return false" name="${todo}-${index}" ${
507
- todo[1] ? 'checked' : ''
508
- }> ${todo[0]}</span><br />
509
- `;
510
-
511
- toDoTarget.appendChild(checkItem);
512
- });
513
-
514
- // run in-browser tests section
515
- const runTestsDiv = document.createElement('div');
516
- runTestsDiv.className = 'tests';
517
- runTestsDiv.innerHTML = '<center><button>Run in-browser tests</button></center';
518
-
519
- runTestsDiv.getElementsByTagName('button')[0].onclick = () => {
520
- runTests();
521
- };
522
-
523
- const testLog = document.createElement('div'); // drop in test log directly into player DOM for demo
524
- testLog.id = 'test-log';
525
- document.body.appendChild(testLog);
526
-
527
- // bind to tests event
528
- let logStarted = false;
529
- let logTimeout;
530
- function doUILog(e) {
531
- if (!logStarted) {
532
- testLog.classList.add('active');
533
- logStarted = true;
534
- }
535
-
536
- if (e.output) {
537
- testLog.innerHTML += `<pre class="${e.logType}">${e.output}</pre>`;
538
- testLog.scrollTop = testLog.scrollHeight;
539
-
540
- if (e.logType === 'final') {
541
- if (logTimeout) {
542
- clearTimeout(logTimeout);
543
- }
544
-
545
- logTimeout = setTimeout(() => {
546
- testLog.classList.remove('active');
547
- logStarted = false;
548
-
549
- setTimeout(() => {
550
- testLog.innerHTML = '';
551
- }, 300); // wait for the animation to complete before purging
552
- }, 5000); // auto-close after 5 seconds
553
- }
554
- }
555
- }
556
- document.addEventListener('log', (e) => {
557
- doUILog(e);
558
- });
559
-
560
- toDoTarget.prepend(runTestsDiv);
561
-
562
- // access changelog and wishlist
563
- window.changelog = () => {
564
- Object.keys(pkgJson.changelog).forEach((changelogKey) => {
565
- console.log(`${changelogKey}:\n\n`);
566
- pkgJson.changelog[changelogKey].forEach((entry) => {
567
- console.log(entry);
568
- });
569
- console.log('\n\n');
570
- });
571
- };
572
-
573
- window.wishlist = () => {
574
- pkgJson.wishlist.reverse().forEach((wish, index) => {
575
- console.log(`${index + 1} - ${wish}`);
576
- });
577
- };
578
-
579
- // footer
580
- const links = [
581
- {
582
- title: 'Docs',
583
- href: 'https://vidispine.github.io/vdt/?path=/story/vdt-videojs-introduction--readme',
584
- },
585
- {
586
- title: 'Use with React',
587
- href: 'https://vidispine.github.io/vdt/?path=/story/vdt-videojs-react-videoplayer--basic',
588
- },
589
- {
590
- title: 'Ask a question',
591
- href: 'mailto:oto.insa@bertelsmann.de',
592
- },
593
- {
594
- title: 'video.js Docs',
595
- href: 'https://docs.videojs.com',
596
- },
597
- {
598
- title: 'npm',
599
- href: 'https://www.npmjs.com/package/@vidispine/vdt-videojs',
600
- },
601
- ];
602
-
603
- const footer = document.createElement('div');
604
- footer.id = 'footer';
605
- links.forEach((link) => {
606
- const linkEl = document.createElement('a');
607
-
608
- linkEl.href = link.href;
609
- linkEl.target = '_BLANK';
610
- linkEl.textContent = link.title;
611
-
612
- footer.appendChild(linkEl);
613
- });
614
- document.body.appendChild(footer);
@@ -1,53 +0,0 @@
1
- WEBVTT
2
-
3
-
4
- 00:00:00.500 --> 00:00:03.500 line:0%
5
- Las aves...
6
-
7
- 00:00:03.800 --> 00:00:05.800 line:0%
8
- Están volando y buceando en el océano.
9
-
10
- 00:00:06.000 --> 00:00:07.000 line:0%
11
- Y ahora...
12
-
13
- 00:00:07.200 --> 00:00:08.000 position:58% line:0%
14
- La fiesta de los pájaros.
15
-
16
- 00:00:08.200 --> 00:00:10.000 line:0%
17
- Yumm, loveley peces!
18
-
19
- 00:00:10.100 --> 00:00:12.000 line:0%
20
- Los peces tienen muchos nutrientes en ellos.
21
-
22
- 00:00:12.000 --> 00:00:13.200 line:0%
23
- ¿Qué pasa si el pez puede sentir dolor?
24
-
25
- 00:00:13.200 --> 00:00:14.500 position:61% line:0%
26
- Es un pensamiento
27
-
28
- 00:00:14.522 --> 00:00:16.900 line:0%
29
- ¿Los peces hablan inglés?
30
-
31
- 00:00:16.950 --> 00:00:18.200 line:0%
32
- Gulp gulp gl gllllp...
33
-
34
- 00:00:18.300 --> 00:00:20.200 line:0%
35
- Tenemos que detener al pez canalla.
36
-
37
- 00:00:20.333 --> 00:00:25.333 line:0%
38
- Swoosh swooosh; zambullirse o morir
39
-
40
- 00:00:26.000 --> 00:00:28.240 line:0%
41
- Esto se ve colosal.
42
-
43
- 00:00:28.951 --> 00:00:30.785 line:0%
44
- Los tengo !!!
45
-
46
- 00:00:30.900 --> 00:00:32.000 line:0%
47
- O-oh, los grandes peces están llegando.
48
-
49
- 00:00:33.200 --> 00:00:36.044 line:0%
50
- "Te vamos a acabar"
51
-
52
- 00:00:36.800 --> 00:00:42.547 line:0%
53
- OK, tengo que rebotar, BALLENA, esto no se ha puesto peor de lo que es; ¡Mira esta larga línea de subtítulos!