shaka-player 3.2.2 → 3.3.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/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
- package/.github/ISSUE_TEMPLATE/feature_request.md +1 -1
- package/.github/ISSUE_TEMPLATE/question.md +1 -1
- package/.github/workflows/build_and_test.yaml +73 -0
- package/.github/workflows/release.yaml +53 -0
- package/AUTHORS +6 -5
- package/CHANGELOG.md +314 -1
- package/CONTRIBUTORS +4 -2
- package/build/checkversion.py +1 -1
- package/build/conformance.textproto +183 -169
- package/build/test.py +6 -0
- package/build/types/core +3 -0
- package/build/types/offline +1 -0
- package/build/types/polyfill +1 -0
- package/build/types/ui +2 -0
- package/demo/common/assets.js +18 -0
- package/demo/common/message_ids.js +13 -1
- package/demo/config.js +31 -1
- package/demo/index.html +1 -1
- package/demo/locales/en.json +12 -0
- package/demo/locales/source.json +48 -0
- package/dist/controls.css +26 -2
- package/dist/controls.css.map +1 -1
- package/dist/demo.compiled.debug.js +19 -16
- package/dist/demo.compiled.debug.map +3 -3
- package/dist/demo.compiled.js +19 -16
- package/dist/demo.compiled.map +3 -3
- package/dist/deps.js +21 -14
- package/dist/locales.js +31 -29
- package/dist/receiver.compiled.debug.js +11 -9
- package/dist/receiver.compiled.debug.map +3 -3
- package/dist/receiver.compiled.js +11 -9
- package/dist/receiver.compiled.map +3 -3
- package/dist/shaka-player.compiled.d.ts +232 -18
- package/dist/shaka-player.compiled.debug.d.ts +232 -18
- package/dist/shaka-player.compiled.debug.externs.js +293 -17
- package/dist/shaka-player.compiled.debug.js +481 -422
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +293 -17
- package/dist/shaka-player.compiled.js +846 -817
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +250 -30
- package/dist/shaka-player.ui.debug.d.ts +250 -30
- package/dist/shaka-player.ui.debug.externs.js +322 -28
- package/dist/shaka-player.ui.debug.js +630 -553
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +322 -28
- package/dist/shaka-player.ui.js +1048 -1006
- package/dist/shaka-player.ui.map +4 -4
- package/docs/design/bg-fetch-after.gv +29 -0
- package/docs/design/bg-fetch-after.gv.png +0 -0
- package/docs/design/bg-fetch-before.gv +18 -0
- package/docs/design/bg-fetch-before.gv.png +0 -0
- package/docs/design/bg-fetch.md +134 -0
- package/docs/tutorials/blob-url.md +15 -0
- package/docs/tutorials/index.json +1 -0
- package/docs/tutorials/ui-customization.md +66 -0
- package/docs/tutorials/ui.md +5 -5
- package/externs/cmcd.js +186 -0
- package/externs/ima.js +3 -0
- package/externs/shaka/ads.js +5 -0
- package/externs/shaka/manifest.js +12 -1
- package/externs/shaka/manifest_parser.js +10 -1
- package/externs/shaka/net.js +20 -4
- package/externs/shaka/offline.js +31 -2
- package/externs/shaka/player.js +138 -5
- package/externs/shaka/text.js +24 -0
- package/externs/webcrypto.js +18 -0
- package/karma.conf.js +19 -15
- package/lib/abr/ewma.js +14 -0
- package/lib/abr/ewma_bandwidth_estimator.js +16 -2
- package/lib/abr/simple_abr_manager.js +6 -0
- package/lib/ads/ad_manager.js +1 -0
- package/lib/ads/client_side_ad.js +8 -0
- package/lib/ads/client_side_ad_manager.js +8 -4
- package/lib/ads/server_side_ad.js +7 -0
- package/lib/cast/cast_proxy.js +3 -0
- package/lib/cast/cast_receiver.js +3 -0
- package/lib/cast/cast_utils.js +1 -0
- package/lib/dash/dash_parser.js +79 -33
- package/lib/dash/segment_base.js +25 -1
- package/lib/dash/segment_list.js +18 -9
- package/lib/dash/segment_template.js +23 -10
- package/lib/hls/hls_parser.js +116 -23
- package/lib/hls/manifest_text_parser.js +1 -0
- package/lib/media/quality_observer.js +274 -0
- package/lib/media/region_observer.js +39 -33
- package/lib/media/region_timeline.js +17 -19
- package/lib/media/segment_index.js +8 -1
- package/lib/media/segment_reference.js +37 -4
- package/lib/media/streaming_engine.js +47 -6
- package/lib/net/http_fetch_plugin.js +31 -11
- package/lib/net/http_plugin_utils.js +1 -0
- package/lib/net/http_xhr_plugin.js +40 -14
- package/lib/net/networking_engine.js +85 -18
- package/lib/offline/download_info.js +68 -0
- package/lib/offline/download_manager.js +13 -5
- package/lib/offline/indexeddb/base_storage_cell.js +35 -0
- package/lib/offline/indexeddb/storage_mechanism.js +1 -1
- package/lib/offline/indexeddb/v5_storage_cell.js +5 -0
- package/lib/offline/storage.js +401 -293
- package/lib/offline/stored_content_utils.js +2 -0
- package/lib/player.js +254 -27
- package/lib/polyfill/media_capabilities.js +30 -15
- package/lib/polyfill/random_uuid.js +52 -0
- package/lib/text/cue.js +18 -0
- package/lib/text/ttml_text_parser.js +109 -60
- package/lib/text/ui_text_displayer.js +19 -13
- package/lib/util/cmcd_manager.js +672 -0
- package/lib/util/error.js +6 -0
- package/lib/util/fake_event_target.js +21 -1
- package/lib/util/language_utils.js +12 -0
- package/lib/util/mutex.js +52 -0
- package/lib/util/periods.js +98 -64
- package/lib/util/player_configuration.js +18 -0
- package/lib/util/stream_utils.js +86 -10
- package/lib/util/xml_utils.js +19 -0
- package/package.json +3 -6
- package/shaka-player.uncompiled.js +3 -0
- package/test/dash/dash_parser_content_protection_unit.js +2 -0
- package/test/dash/dash_parser_live_unit.js +144 -1
- package/test/dash/dash_parser_manifest_unit.js +37 -2
- package/test/dash/dash_parser_segment_base_unit.js +2 -0
- package/test/dash/dash_parser_segment_list_unit.js +2 -1
- package/test/dash/dash_parser_segment_template_unit.js +2 -1
- package/test/hls/hls_live_unit.js +2 -0
- package/test/hls/hls_parser_unit.js +76 -0
- package/test/media/drm_engine_unit.js +3 -3
- package/test/media/quality_observer_unit.js +142 -0
- package/test/media/region_observer_unit.js +40 -5
- package/test/media/region_timeline_unit.js +40 -22
- package/test/media/segment_reference_unit.js +16 -1
- package/test/media/streaming_engine_integration.js +4 -2
- package/test/media/streaming_engine_unit.js +147 -0
- package/test/net/http_plugin_unit.js +20 -15
- package/test/offline/storage_integration.js +108 -4
- package/test/player_integration.js +140 -8
- package/test/player_src_equals_integration.js +41 -8
- package/test/player_unit.js +19 -9
- package/test/test/assets/chapters.vtt +2 -2
- package/test/test/assets/chapters2.vtt +10 -0
- package/test/test/util/dash_parser_util.js +4 -0
- package/test/test/util/fake_ad.js +7 -0
- package/test/test/util/fake_drm_engine.js +9 -2
- package/test/test/util/manifest_generator.js +3 -2
- package/test/test/util/test_scheme.js +14 -1
- package/test/test/util/ttml_utils.js +81 -0
- package/test/test/util/util.js +10 -7
- package/test/text/mp4_ttml_parser_unit.js +11 -18
- package/test/text/ttml_text_parser_unit.js +544 -228
- package/test/ui/text_displayer_layout_unit.js +8 -7
- package/test/ui/ui_unit.js +250 -0
- package/test/util/cmcd_manager_unit.js +291 -0
- package/test/util/stream_utils_unit.js +36 -30
- package/third_party/closure-uri/uri.js +21 -0
- package/ui/airplay_button.js +1 -0
- package/ui/audio_language_selection.js +7 -0
- package/ui/cast_button.js +1 -0
- package/ui/context_menu.js +109 -0
- package/ui/controls.js +44 -7
- package/ui/controls.less +1 -0
- package/ui/enums.js +4 -0
- package/ui/externs/ui.js +28 -1
- package/ui/fast_forward_button.js +14 -5
- package/ui/fullscreen_button.js +1 -0
- package/ui/less/buttons.less +0 -4
- package/ui/less/containers.less +74 -0
- package/ui/less/overflow_menu.less +0 -10
- package/ui/less/tooltip.less +97 -0
- package/ui/locales/en.json +1 -0
- package/ui/locales/source.json +4 -0
- package/ui/localization.js +12 -36
- package/ui/loop_button.js +5 -0
- package/ui/mute_button.js +2 -1
- package/ui/overflow_menu.js +5 -25
- package/ui/pip_button.js +5 -0
- package/ui/play_button.js +2 -2
- package/ui/playback_rate_selection.js +8 -9
- package/ui/resolution_selection.js +7 -0
- package/ui/rewind_button.js +15 -6
- package/ui/seek_bar.js +4 -2
- package/ui/settings_menu.js +36 -4
- package/ui/small_play_button.js +1 -0
- package/ui/statistics_button.js +253 -0
- package/ui/text_selection.js +12 -4
- package/ui/ui.js +30 -0
- package/ui/volume_bar.js +1 -1
- package/externs/media_capabilities.js +0 -16
|
@@ -34,11 +34,8 @@ filterDescribe('TextDisplayer layout', supportsScreenshots, () => {
|
|
|
34
34
|
*/
|
|
35
35
|
let beforeScreenshot;
|
|
36
36
|
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
// to have this change tolerance in our tests. So far, all past bugs in our
|
|
40
|
-
// implementation that we have tests for would exceed this threshold by a lot.
|
|
41
|
-
const threshold = 160; // px
|
|
37
|
+
// A minimum similarity score for screenshots, between 0 and 1.
|
|
38
|
+
const minSimilarity = 0.95;
|
|
42
39
|
|
|
43
40
|
const originalCast = window.chrome && window.chrome.cast;
|
|
44
41
|
|
|
@@ -196,6 +193,7 @@ filterDescribe('TextDisplayer layout', supportsScreenshots, () => {
|
|
|
196
193
|
|
|
197
194
|
// The video must be played a little now, after the cues were appended,
|
|
198
195
|
// but before the screenshot.
|
|
196
|
+
video.playbackRate = 1;
|
|
199
197
|
video.play();
|
|
200
198
|
await waiter.failOnTimeout(false).timeoutAfter(5)
|
|
201
199
|
.waitForMovement(video);
|
|
@@ -203,10 +201,13 @@ filterDescribe('TextDisplayer layout', supportsScreenshots, () => {
|
|
|
203
201
|
|
|
204
202
|
// Seek to a time when cues should be showing.
|
|
205
203
|
video.currentTime = time;
|
|
204
|
+
// Get into a playing state, but without movement.
|
|
205
|
+
video.playbackRate = 0;
|
|
206
|
+
video.play();
|
|
206
207
|
|
|
207
208
|
// Add a short delay to ensure that the system has caught up and that
|
|
208
209
|
// native text displayers have been updated by the browser.
|
|
209
|
-
await Util.
|
|
210
|
+
await Util.delay(0.1);
|
|
210
211
|
};
|
|
211
212
|
});
|
|
212
213
|
|
|
@@ -475,6 +476,6 @@ filterDescribe('TextDisplayer layout', supportsScreenshots, () => {
|
|
|
475
476
|
return Util.checkScreenshot(
|
|
476
477
|
/* element= */ videoContainer,
|
|
477
478
|
prefix + '-' + baseName,
|
|
478
|
-
|
|
479
|
+
minSimilarity);
|
|
479
480
|
}
|
|
480
481
|
});
|
package/test/ui/ui_unit.js
CHANGED
|
@@ -409,6 +409,80 @@ describe('UI', () => {
|
|
|
409
409
|
});
|
|
410
410
|
});
|
|
411
411
|
|
|
412
|
+
describe('control panel buttons with submenus', () => {
|
|
413
|
+
/** @type {!HTMLElement} */
|
|
414
|
+
let resolutionMenu;
|
|
415
|
+
/** @type {!Element} */
|
|
416
|
+
let resolutionMenuButton;
|
|
417
|
+
/** @type {!HTMLElement} */
|
|
418
|
+
let languageMenu;
|
|
419
|
+
/** @type {!Element} */
|
|
420
|
+
let languageMenuButton;
|
|
421
|
+
|
|
422
|
+
beforeEach(() => {
|
|
423
|
+
const config = {
|
|
424
|
+
controlPanelElements: [
|
|
425
|
+
'quality',
|
|
426
|
+
'language',
|
|
427
|
+
],
|
|
428
|
+
};
|
|
429
|
+
const ui = UiUtils.createUIThroughAPI(videoContainer, video, config);
|
|
430
|
+
player = ui.getControls().getLocalPlayer();
|
|
431
|
+
|
|
432
|
+
const resolutionsMenus =
|
|
433
|
+
videoContainer.getElementsByClassName('shaka-resolutions');
|
|
434
|
+
expect(resolutionsMenus.length).toBe(1);
|
|
435
|
+
resolutionMenu = /** @type {!HTMLElement} */ (resolutionsMenus[0]);
|
|
436
|
+
|
|
437
|
+
const resolutionMenuButtons =
|
|
438
|
+
videoContainer.getElementsByClassName('shaka-resolution-button');
|
|
439
|
+
expect(resolutionMenuButtons.length).toBe(1);
|
|
440
|
+
resolutionMenuButton = resolutionMenuButtons[0];
|
|
441
|
+
|
|
442
|
+
const languageMenus =
|
|
443
|
+
videoContainer.getElementsByClassName('shaka-audio-languages');
|
|
444
|
+
expect(languageMenus.length).toBe(1);
|
|
445
|
+
languageMenu = /** @type {!HTMLElement} */ (languageMenus[0]);
|
|
446
|
+
|
|
447
|
+
const languageMenuButtons =
|
|
448
|
+
videoContainer.getElementsByClassName('shaka-language-button');
|
|
449
|
+
expect(languageMenuButtons.length).toBe(1);
|
|
450
|
+
languageMenuButton = languageMenuButtons[0];
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
it('menus are initially hidden', () => {
|
|
454
|
+
expect(resolutionMenu.classList.contains('shaka-hidden')).toBe(true);
|
|
455
|
+
expect(languageMenu.classList.contains('shaka-hidden')).toBe(true);
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
it('a menu becomes visible if the button is clicked', () => {
|
|
459
|
+
resolutionMenuButton.click();
|
|
460
|
+
|
|
461
|
+
expect(resolutionMenu.classList.contains('shaka-hidden')).toBe(false);
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
it('a menu becomes hidden if the "close" button is clicked', () => {
|
|
465
|
+
resolutionMenuButton.click();
|
|
466
|
+
|
|
467
|
+
const backToOverflowButtons =
|
|
468
|
+
videoContainer.getElementsByClassName('shaka-back-to-overflow-button');
|
|
469
|
+
expect(backToOverflowButtons.length).toBe(2);
|
|
470
|
+
const backToOverflowButton =
|
|
471
|
+
/** @type {!HTMLElement} */ (backToOverflowButtons[0]);
|
|
472
|
+
backToOverflowButton.click();
|
|
473
|
+
|
|
474
|
+
expect(resolutionMenu.classList.contains('shaka-hidden')).toBe(true);
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
it('a menu becomes hidden if another one is opened', () => {
|
|
478
|
+
resolutionMenuButton.click();
|
|
479
|
+
languageMenuButton.click();
|
|
480
|
+
|
|
481
|
+
expect(resolutionMenu.classList.contains('shaka-hidden')).toBe(true);
|
|
482
|
+
expect(languageMenu.classList.contains('shaka-hidden')).toBe(false);
|
|
483
|
+
});
|
|
484
|
+
});
|
|
485
|
+
|
|
412
486
|
describe('resolutions menu', () => {
|
|
413
487
|
/** @type {!HTMLElement} */
|
|
414
488
|
let resolutionsMenu;
|
|
@@ -611,6 +685,182 @@ describe('UI', () => {
|
|
|
611
685
|
goog.asserts.assert(found, 'Unable to find resolution menu');
|
|
612
686
|
}
|
|
613
687
|
});
|
|
688
|
+
|
|
689
|
+
describe('custom context menu', () => {
|
|
690
|
+
/** @type {!HTMLElement} */
|
|
691
|
+
let controlsContainer;
|
|
692
|
+
/** @type {!HTMLElement} */
|
|
693
|
+
let contextMenu;
|
|
694
|
+
|
|
695
|
+
beforeEach(() => {
|
|
696
|
+
const config = {
|
|
697
|
+
customContextMenu: true,
|
|
698
|
+
contextMenuElements: [
|
|
699
|
+
'fakeElement',
|
|
700
|
+
'statistics',
|
|
701
|
+
'fakeElement',
|
|
702
|
+
],
|
|
703
|
+
};
|
|
704
|
+
const ui = UiUtils.createUIThroughAPI(videoContainer, video, config);
|
|
705
|
+
|
|
706
|
+
controlsContainer = ui.getControls().getControlsContainer();
|
|
707
|
+
|
|
708
|
+
const contextMenus =
|
|
709
|
+
videoContainer.getElementsByClassName('shaka-context-menu');
|
|
710
|
+
expect(contextMenus.length).toBe(1);
|
|
711
|
+
contextMenu = /** @type {!HTMLElement} */
|
|
712
|
+
(contextMenus[0]);
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
it('responds to contextmenu event', () => {
|
|
716
|
+
expect(contextMenu.classList.contains('shaka-hidden')).toBe(true);
|
|
717
|
+
UiUtils.simulateEvent(controlsContainer, 'contextmenu');
|
|
718
|
+
expect(contextMenu.classList.contains('shaka-hidden')).toBe(false);
|
|
719
|
+
UiUtils.simulateEvent(controlsContainer, 'contextmenu');
|
|
720
|
+
expect(contextMenu.classList.contains('shaka-hidden')).toBe(true);
|
|
721
|
+
});
|
|
722
|
+
it('hides on click event', () => {
|
|
723
|
+
UiUtils.simulateEvent(controlsContainer, 'contextmenu');
|
|
724
|
+
UiUtils.simulateEvent(controlsContainer, 'click');
|
|
725
|
+
expect(contextMenu.classList.contains('shaka-hidden')).toBe(true);
|
|
726
|
+
UiUtils.simulateEvent(controlsContainer, 'contextmenu');
|
|
727
|
+
UiUtils.simulateEvent(window, 'click');
|
|
728
|
+
expect(contextMenu.classList.contains('shaka-hidden')).toBe(true);
|
|
729
|
+
});
|
|
730
|
+
it('builds internal elements', () => {
|
|
731
|
+
expect(contextMenu.childNodes.length).toBe(1);
|
|
732
|
+
|
|
733
|
+
expect(contextMenu.childNodes[0]['className'])
|
|
734
|
+
.toBe('shaka-statistics-button');
|
|
735
|
+
});
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
describe('statistics context menu', () => {
|
|
739
|
+
/** @type {!HTMLElement} */
|
|
740
|
+
let statisticsButton;
|
|
741
|
+
/** @type {!HTMLElement} */
|
|
742
|
+
let statisticsContainer;
|
|
743
|
+
|
|
744
|
+
beforeEach(() => {
|
|
745
|
+
const config = {
|
|
746
|
+
customContextMenu: true,
|
|
747
|
+
contextMenuElements: [
|
|
748
|
+
'statistics',
|
|
749
|
+
],
|
|
750
|
+
statisticsList: Object.keys(new shaka.util.Stats().getBlob()),
|
|
751
|
+
};
|
|
752
|
+
const ui = UiUtils.createUIThroughAPI(videoContainer, video, config);
|
|
753
|
+
player = ui.getControls().getLocalPlayer();
|
|
754
|
+
|
|
755
|
+
const statisticsButtons =
|
|
756
|
+
videoContainer.getElementsByClassName('shaka-statistics-button');
|
|
757
|
+
expect(statisticsButtons.length).toBe(1);
|
|
758
|
+
statisticsButton = /** @type {!HTMLElement} */
|
|
759
|
+
(statisticsButtons[0]);
|
|
760
|
+
|
|
761
|
+
const statisticsContainers =
|
|
762
|
+
videoContainer.getElementsByClassName('shaka-statistics-container');
|
|
763
|
+
expect(statisticsContainers.length).toBe(1);
|
|
764
|
+
statisticsContainer = /** @type {!HTMLElement} */
|
|
765
|
+
(statisticsContainers[0]);
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
it('appears and disappears on toggle', () => {
|
|
769
|
+
expect(statisticsContainer.classList.contains('shaka-hidden'))
|
|
770
|
+
.toBe(true);
|
|
771
|
+
|
|
772
|
+
statisticsButton.click();
|
|
773
|
+
expect(statisticsContainer.classList.contains('shaka-hidden'))
|
|
774
|
+
.toBe(false);
|
|
775
|
+
|
|
776
|
+
statisticsButton.click();
|
|
777
|
+
expect(statisticsContainer.classList.contains('shaka-hidden'))
|
|
778
|
+
.toBe(true);
|
|
779
|
+
});
|
|
780
|
+
|
|
781
|
+
it('displays all the available statistics', () => {
|
|
782
|
+
const skippedStats = ['stateHistory', 'switchHistory'];
|
|
783
|
+
const nodes = statisticsContainer.childNodes;
|
|
784
|
+
let nodeIndex = 0;
|
|
785
|
+
|
|
786
|
+
for (const statistic in new shaka.util.Stats().getBlob()) {
|
|
787
|
+
if (!skippedStats.includes(statistic)) {
|
|
788
|
+
// Text content of label (without ':') is a valid statistic
|
|
789
|
+
const label = nodes[nodeIndex].childNodes[0].textContent;
|
|
790
|
+
expect(label.replace(':', '')).toBe(statistic);
|
|
791
|
+
|
|
792
|
+
// Value has been parsed and it is not the default 'NaN'
|
|
793
|
+
const value = nodes[nodeIndex].childNodes[1].textContent;
|
|
794
|
+
expect(value).not.toBe('NaN');
|
|
795
|
+
|
|
796
|
+
nodeIndex += 1;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
});
|
|
800
|
+
it('is updated periodically', async () => {
|
|
801
|
+
function getStatsFromContainer() {
|
|
802
|
+
const nodes = statisticsContainer.childNodes;
|
|
803
|
+
width = nodes[0].childNodes[1].textContent.replace(' (px)', '');
|
|
804
|
+
height = nodes[1].childNodes[1].textContent.replace(' (px)', '');
|
|
805
|
+
bufferingTime =
|
|
806
|
+
nodes[13].childNodes[1].textContent.replace(' (s)', '');
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
/** @type {!string} */
|
|
810
|
+
let width;
|
|
811
|
+
/** @type {!string} */
|
|
812
|
+
let height;
|
|
813
|
+
/** @type {!string} */
|
|
814
|
+
let bufferingTime;
|
|
815
|
+
/** @type {!string} */
|
|
816
|
+
let lastBufferingTime;
|
|
817
|
+
|
|
818
|
+
const manifest =
|
|
819
|
+
shaka.test.ManifestGenerator.generate((manifest) => {
|
|
820
|
+
manifest.addVariant(/* id= */ 0, (variant) => {
|
|
821
|
+
variant.addVideo(1, (stream) => {
|
|
822
|
+
stream.size(1920, 1080);
|
|
823
|
+
});
|
|
824
|
+
});
|
|
825
|
+
});
|
|
826
|
+
|
|
827
|
+
shaka.media.ManifestParser.registerParserByMime(
|
|
828
|
+
fakeMimeType, () => new shaka.test.FakeManifestParser(manifest));
|
|
829
|
+
|
|
830
|
+
await player.load(
|
|
831
|
+
/* uri= */ 'fake', /* startTime= */ 0, fakeMimeType);
|
|
832
|
+
|
|
833
|
+
// Placeholder statistics are available before toggle
|
|
834
|
+
getStatsFromContainer();
|
|
835
|
+
expect(width).toBe('NaN');
|
|
836
|
+
expect(height).toBe('NaN');
|
|
837
|
+
expect(bufferingTime).toBe('NaN');
|
|
838
|
+
|
|
839
|
+
// Statistics are displayed on toggle
|
|
840
|
+
statisticsButton.click();
|
|
841
|
+
await Util.delay(0.2);
|
|
842
|
+
|
|
843
|
+
getStatsFromContainer();
|
|
844
|
+
expect(width).toBe('1920');
|
|
845
|
+
expect(height).toBe('1080');
|
|
846
|
+
expect(bufferingTime).toBeGreaterThanOrEqual(0.1);
|
|
847
|
+
|
|
848
|
+
// Statistics are updated over time
|
|
849
|
+
lastBufferingTime = bufferingTime;
|
|
850
|
+
await Util.delay(0.2);
|
|
851
|
+
|
|
852
|
+
getStatsFromContainer();
|
|
853
|
+
expect(bufferingTime).toBeGreaterThan(lastBufferingTime);
|
|
854
|
+
|
|
855
|
+
// Statistics stop updating when the container is hidden
|
|
856
|
+
statisticsButton.click();
|
|
857
|
+
lastBufferingTime = bufferingTime;
|
|
858
|
+
await Util.delay(0.2);
|
|
859
|
+
|
|
860
|
+
getStatsFromContainer();
|
|
861
|
+
expect(bufferingTime).toBe(lastBufferingTime);
|
|
862
|
+
});
|
|
863
|
+
});
|
|
614
864
|
});
|
|
615
865
|
|
|
616
866
|
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/*! @license
|
|
2
|
+
* Shaka Player
|
|
3
|
+
* Copyright 2016 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
goog.require('shaka.util.CmcdManager');
|
|
8
|
+
goog.require('shaka.util.ObjectUtils');
|
|
9
|
+
|
|
10
|
+
describe('CmcdManager', () => {
|
|
11
|
+
const CmcdManager = shaka.util.CmcdManager;
|
|
12
|
+
const uuidRegex =
|
|
13
|
+
'[A-F\\d]{8}-[A-F\\d]{4}-4[A-F\\d]{3}-[89AB][A-F\\d]{3}-[A-F\\d]{12}';
|
|
14
|
+
const data = {
|
|
15
|
+
'sid': 'c936730c-031e-4a73-976f-92bc34039c60',
|
|
16
|
+
'cid': 'xyz',
|
|
17
|
+
'su': false,
|
|
18
|
+
'nor': '../testing/3.m4v',
|
|
19
|
+
'nrr': '0-99',
|
|
20
|
+
'd': 6066.66,
|
|
21
|
+
'mtp': 10049,
|
|
22
|
+
'bs': true,
|
|
23
|
+
'br': 52317,
|
|
24
|
+
'v': 1,
|
|
25
|
+
'pr': 1,
|
|
26
|
+
'com.test-hello': 'world',
|
|
27
|
+
'com.test-testing': 1234,
|
|
28
|
+
'com.test-exists': true,
|
|
29
|
+
'com.test-notExists': false,
|
|
30
|
+
'com.test-token': Symbol('s'),
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
describe('Query serialization', () => {
|
|
34
|
+
it('produces correctly serialized data', () => {
|
|
35
|
+
const query = CmcdManager.toQuery(data);
|
|
36
|
+
const result = 'br=52317,bs,cid="xyz",com.test-exists,' +
|
|
37
|
+
'com.test-hello="world",com.test-testing=1234,' +
|
|
38
|
+
'com.test-token=s,d=6067,mtp=10000,' +
|
|
39
|
+
'nor="..%2Ftesting%2F3.m4v",nrr="0-99",' +
|
|
40
|
+
'sid="c936730c-031e-4a73-976f-92bc34039c60"';
|
|
41
|
+
expect(query).toBe(result);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('escapes reserve character in string values', () => {
|
|
45
|
+
const query = CmcdManager.toQuery({
|
|
46
|
+
'com.test-escape': 'Double "Quotes"',
|
|
47
|
+
});
|
|
48
|
+
const result = 'com.test-escape="Double \\"Quotes\\""';
|
|
49
|
+
expect(query).toBe(result);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('Header serialization', () => {
|
|
54
|
+
it('produces all header shards', () => {
|
|
55
|
+
const header = CmcdManager.toHeaders(data);
|
|
56
|
+
expect(header).toEqual({
|
|
57
|
+
'CMCD-Object': 'br=52317,d=6067',
|
|
58
|
+
'CMCD-Request': 'com.test-exists,com.test-hello="world",' +
|
|
59
|
+
'com.test-testing=1234,com.test-token=s,mtp=10000,' +
|
|
60
|
+
'nor="..%2Ftesting%2F3.m4v",nrr="0-99"',
|
|
61
|
+
'CMCD-Session': 'cid="xyz",sid="c936730c-031e-4a73-976f-92bc34039c60"',
|
|
62
|
+
'CMCD-Status': 'bs',
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('ignores empty shards', () => {
|
|
67
|
+
expect(CmcdManager.toHeaders({br: 200})).toEqual({
|
|
68
|
+
'CMCD-Object': 'br=200',
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('CmcdManager instance', () => {
|
|
74
|
+
const ObjectUtils = shaka.util.ObjectUtils;
|
|
75
|
+
|
|
76
|
+
const playerInterface = {
|
|
77
|
+
isLive: () => false,
|
|
78
|
+
getBandwidthEstimate: () => 10000000,
|
|
79
|
+
getBufferedInfo: () => ({
|
|
80
|
+
video: [
|
|
81
|
+
{start: 0, end: 5},
|
|
82
|
+
{start: 6, end: 31.234},
|
|
83
|
+
{start: 35, end: 40},
|
|
84
|
+
],
|
|
85
|
+
}),
|
|
86
|
+
getVariantTracks: () => /** @type {Array.<shaka.extern.Track>} */([
|
|
87
|
+
{
|
|
88
|
+
type: 'variant',
|
|
89
|
+
bandwidth: 50000,
|
|
90
|
+
videoBandwidth: 40000,
|
|
91
|
+
audioBandWidth: 10000,
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
type: 'variant',
|
|
95
|
+
bandwidth: 5000000,
|
|
96
|
+
videoBandwidth: 4000000,
|
|
97
|
+
audioBandWidth: 1000000,
|
|
98
|
+
},
|
|
99
|
+
]),
|
|
100
|
+
getPlaybackRate: () => 1,
|
|
101
|
+
getCurrentTime: () => 10,
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const sid = '2ed2d1cd-970b-48f2-bfb3-50a79e87cfa3';
|
|
105
|
+
const config = {
|
|
106
|
+
enabled: false,
|
|
107
|
+
sessionId: '',
|
|
108
|
+
contentId: 'testing',
|
|
109
|
+
useHeaders: false,
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/** @type shaka.util.CmcdManager */
|
|
113
|
+
let cmcdManager = new CmcdManager(playerInterface, config);
|
|
114
|
+
|
|
115
|
+
const request = {
|
|
116
|
+
uris: ['https://test.com/test.mpd'],
|
|
117
|
+
method: 'GET',
|
|
118
|
+
body: null,
|
|
119
|
+
headers: {
|
|
120
|
+
testing: '1234',
|
|
121
|
+
},
|
|
122
|
+
allowCrossSiteCredentials: false,
|
|
123
|
+
retryParameters: /** @type {shaka.extern.RetryParameters} */({}),
|
|
124
|
+
licenseRequestType: null,
|
|
125
|
+
sessionId: null,
|
|
126
|
+
streamDataCallback: null,
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const manifestInfo = {
|
|
130
|
+
format: shaka.util.CmcdManager.StreamingFormat.DASH,
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const segmentInfo = {
|
|
134
|
+
type: 'video',
|
|
135
|
+
init: false,
|
|
136
|
+
duration: 3.33,
|
|
137
|
+
mimeType: 'application/mp4',
|
|
138
|
+
codecs: 'avc1.42001e',
|
|
139
|
+
bandwidth: 5234167,
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
describe('configuration', () => {
|
|
143
|
+
it('does not modify requests when disabled', () => {
|
|
144
|
+
const r = ObjectUtils.cloneObject(request);
|
|
145
|
+
|
|
146
|
+
cmcdManager.applyManifestData(r, manifestInfo);
|
|
147
|
+
expect(r.uris[0]).toBe(request.uris[0]);
|
|
148
|
+
|
|
149
|
+
cmcdManager.applySegmentData(r, segmentInfo);
|
|
150
|
+
expect(r.uris[0]).toBe(request.uris[0]);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('generates a session id if not provided', () => {
|
|
154
|
+
config.enabled = true;
|
|
155
|
+
cmcdManager = new CmcdManager(playerInterface, config);
|
|
156
|
+
|
|
157
|
+
const r = ObjectUtils.cloneObject(request);
|
|
158
|
+
|
|
159
|
+
cmcdManager.applyManifestData(r, manifestInfo);
|
|
160
|
+
const regex = new RegExp(`sid%3D%22${uuidRegex}%22`, 'i');
|
|
161
|
+
expect(regex.test(r.uris[0])).toBe(true);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
describe('query mode', () => {
|
|
166
|
+
beforeAll(() => {
|
|
167
|
+
config.sessionId = sid;
|
|
168
|
+
cmcdManager = new CmcdManager(playerInterface, config);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('modifies manifest request uris', () => {
|
|
172
|
+
const r = ObjectUtils.cloneObject(request);
|
|
173
|
+
cmcdManager.applyManifestData(r, manifestInfo);
|
|
174
|
+
const uri = 'https://test.com/test.mpd?CMCD=cid%3D%22testing%22%2C' +
|
|
175
|
+
'mtp%3D10000%2Cot%3Dm%2Csf%3Dd%2C' +
|
|
176
|
+
'sid%3D%222ed2d1cd-970b-48f2-bfb3-50a79e87cfa3%22%2Csu';
|
|
177
|
+
expect(r.uris[0]).toBe(uri);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('modifies segment request uris', () => {
|
|
181
|
+
const r = ObjectUtils.cloneObject(request);
|
|
182
|
+
cmcdManager.applySegmentData(r, segmentInfo);
|
|
183
|
+
const uri = 'https://test.com/test.mpd?CMCD=bl%3D21200%2Cbr%3D5234%2Ccid%3D%22' +
|
|
184
|
+
'testing%22%2Cd%3D3330%2Cmtp%3D10000%2Cot%3Dv%2Csf%3Dd%2C' +
|
|
185
|
+
'sid%3D%222ed2d1cd-970b-48f2-bfb3-50a79e87cfa3%22%2Cst%3Dv%2Csu%2C' +
|
|
186
|
+
'tb%3D4000';
|
|
187
|
+
expect(r.uris[0]).toBe(uri);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('modifies text request uris', () => {
|
|
191
|
+
const r = ObjectUtils.cloneObject(request);
|
|
192
|
+
cmcdManager.applyTextData(r);
|
|
193
|
+
const uri = 'https://test.com/test.mpd?CMCD=cid%3D%22' +
|
|
194
|
+
'testing%22%2Cmtp%3D10000%2Cot%3Dc%2Csf%3Dd%2C' +
|
|
195
|
+
'sid%3D%222ed2d1cd-970b-48f2-bfb3-50a79e87cfa3%22%2Csu';
|
|
196
|
+
expect(r.uris[0]).toBe(uri);
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
describe('header mode', () => {
|
|
201
|
+
beforeAll(() => {
|
|
202
|
+
config.useHeaders = true;
|
|
203
|
+
cmcdManager = new CmcdManager(playerInterface, config);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('modifies manifest request headers', () => {
|
|
207
|
+
const r = ObjectUtils.cloneObject(request);
|
|
208
|
+
cmcdManager.applyManifestData(r, manifestInfo);
|
|
209
|
+
expect(r.headers).toEqual({
|
|
210
|
+
'testing': '1234',
|
|
211
|
+
'CMCD-Object': 'ot=m',
|
|
212
|
+
'CMCD-Request': 'mtp=10000,su',
|
|
213
|
+
'CMCD-Session': 'cid="testing",sf=d,' +
|
|
214
|
+
'sid="2ed2d1cd-970b-48f2-bfb3-50a79e87cfa3"',
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('modifies segment request headers', () => {
|
|
219
|
+
const r = ObjectUtils.cloneObject(request);
|
|
220
|
+
cmcdManager.applySegmentData(r, segmentInfo);
|
|
221
|
+
expect(r.headers).toEqual({
|
|
222
|
+
'testing': '1234',
|
|
223
|
+
'CMCD-Object': 'br=5234,d=3330,ot=v,tb=4000',
|
|
224
|
+
'CMCD-Request': 'bl=21200,mtp=10000,su',
|
|
225
|
+
'CMCD-Session': 'cid="testing",sf=d,' +
|
|
226
|
+
'sid="2ed2d1cd-970b-48f2-bfb3-50a79e87cfa3",st=v',
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
describe('src= mode', () => {
|
|
232
|
+
it('modifies media stream uris', () => {
|
|
233
|
+
const r = cmcdManager
|
|
234
|
+
.appendSrcData('https://test.com/test.mp4', 'video/mp4');
|
|
235
|
+
const uri = 'https://test.com/test.mp4?CMCD=cid%3D%22testing%22%2C' +
|
|
236
|
+
'mtp%3D10000%2Cot%3Dav%2Csf%3Dd%2C' +
|
|
237
|
+
'sid%3D%222ed2d1cd-970b-48f2-bfb3-50a79e87cfa3%22%2Csu';
|
|
238
|
+
expect(r).toBe(uri);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('modifies manifest stream uris', () => {
|
|
242
|
+
const r = cmcdManager
|
|
243
|
+
.appendSrcData('https://test.com/test.m3u8', 'application/x-mpegurl');
|
|
244
|
+
const uri = 'https://test.com/test.m3u8?CMCD=cid%3D%22testing%22%2C' +
|
|
245
|
+
'mtp%3D10000%2Cot%3Dm%2Csf%3Dd%2C' +
|
|
246
|
+
'sid%3D%222ed2d1cd-970b-48f2-bfb3-50a79e87cfa3%22%2Csu';
|
|
247
|
+
expect(r).toBe(uri);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('modifies text track uris', () => {
|
|
251
|
+
const r = cmcdManager.appendTextTrackData('https://test.com/test.vtt');
|
|
252
|
+
const uri = 'https://test.com/test.vtt?CMCD=cid%3D%22testing%22%2C' +
|
|
253
|
+
'mtp%3D10000%2Cot%3Dc%2Csf%3Dd%2C' +
|
|
254
|
+
'sid%3D%222ed2d1cd-970b-48f2-bfb3-50a79e87cfa3%22%2Csu';
|
|
255
|
+
expect(r).toBe(uri);
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
describe('adheres to the spec', () => {
|
|
260
|
+
beforeAll(() => {
|
|
261
|
+
cmcdManager.setBuffering(false);
|
|
262
|
+
cmcdManager.setBuffering(true);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('sends bs only once', () => {
|
|
266
|
+
let r = ObjectUtils.cloneObject(request);
|
|
267
|
+
cmcdManager.applySegmentData(r, segmentInfo);
|
|
268
|
+
expect(r.headers['CMCD-Status']).toContain('bs');
|
|
269
|
+
|
|
270
|
+
r = ObjectUtils.cloneObject(request);
|
|
271
|
+
cmcdManager.applySegmentData(r, segmentInfo);
|
|
272
|
+
expect(r.headers['CMCD-Status']).not.toContain('bs');
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('sends su until buffering is complete', () => {
|
|
276
|
+
let r = ObjectUtils.cloneObject(request);
|
|
277
|
+
cmcdManager.applySegmentData(r, segmentInfo);
|
|
278
|
+
expect(r.headers['CMCD-Request']).toContain(',su');
|
|
279
|
+
|
|
280
|
+
r = ObjectUtils.cloneObject(request);
|
|
281
|
+
cmcdManager.applySegmentData(r, segmentInfo);
|
|
282
|
+
expect(r.headers['CMCD-Request']).toContain(',su');
|
|
283
|
+
|
|
284
|
+
cmcdManager.setBuffering(false);
|
|
285
|
+
r = ObjectUtils.cloneObject(request);
|
|
286
|
+
cmcdManager.applySegmentData(r, segmentInfo);
|
|
287
|
+
expect(r.headers['CMCD-Request']).not.toContain(',su');
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
});
|