shaka-player 4.10.19 → 4.10.21
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/README.md +2 -4
- package/dist/deps.js +1 -1
- package/dist/locales.js +37 -37
- package/dist/shaka-player.compiled.debug.js +45 -42
- package/dist/shaka-player.compiled.debug.map +3 -3
- package/dist/shaka-player.compiled.js +945 -942
- package/dist/shaka-player.compiled.map +3 -3
- package/dist/shaka-player.ui.debug.js +48 -45
- package/dist/shaka-player.ui.debug.map +3 -3
- package/dist/shaka-player.ui.js +1130 -1127
- package/dist/shaka-player.ui.map +3 -3
- package/lib/ads/interstitial_ad_manager.js +4 -0
- package/lib/media/preload_manager.js +20 -0
- package/lib/media/streaming_engine.js +43 -12
- package/lib/player.js +11 -11
- package/lib/polyfill/media_capabilities.js +8 -2
- package/lib/util/periods.js +3 -0
- package/package.json +1 -1
- package/ui/locales/zh-HK.json +30 -11
|
@@ -199,6 +199,10 @@ shaka.ads.InterstitialAdManager = class {
|
|
|
199
199
|
if (this.adContainer_) {
|
|
200
200
|
shaka.util.Dom.removeAllChildren(this.adContainer_);
|
|
201
201
|
}
|
|
202
|
+
if (this.pollTimer_) {
|
|
203
|
+
this.pollTimer_.stop();
|
|
204
|
+
this.pollTimer_ = null;
|
|
205
|
+
}
|
|
202
206
|
this.player_.destroy();
|
|
203
207
|
}
|
|
204
208
|
|
|
@@ -127,6 +127,9 @@ shaka.media.PreloadManager = class extends shaka.util.FakeEventTarget {
|
|
|
127
127
|
/** @private {!shaka.util.Stats} */
|
|
128
128
|
this.stats_ = new shaka.util.Stats();
|
|
129
129
|
|
|
130
|
+
/** @private {!shaka.util.PublicPromise} */
|
|
131
|
+
this.manifestPromise_ = new shaka.util.PublicPromise();
|
|
132
|
+
|
|
130
133
|
/** @private {!shaka.util.PublicPromise} */
|
|
131
134
|
this.successPromise_ = new shaka.util.PublicPromise();
|
|
132
135
|
|
|
@@ -490,10 +493,15 @@ shaka.media.PreloadManager = class extends shaka.util.FakeEventTarget {
|
|
|
490
493
|
this.assetUri_, this.manifestPlayerInterface_);
|
|
491
494
|
}
|
|
492
495
|
|
|
496
|
+
this.manifestPromise_.resolve();
|
|
497
|
+
|
|
493
498
|
// This event is fired after the manifest is parsed, but before any
|
|
494
499
|
// filtering takes place.
|
|
495
500
|
const event =
|
|
496
501
|
this.makeEvent_(shaka.util.FakeEvent.EventName.ManifestParsed);
|
|
502
|
+
// Delay event to ensure manifest has been properly propagated
|
|
503
|
+
// to the player.
|
|
504
|
+
await Promise.resolve();
|
|
497
505
|
this.dispatchEvent(event);
|
|
498
506
|
|
|
499
507
|
// We require all manifests to have at least one variant.
|
|
@@ -700,6 +708,18 @@ shaka.media.PreloadManager = class extends shaka.util.FakeEventTarget {
|
|
|
700
708
|
return this.successPromise_;
|
|
701
709
|
}
|
|
702
710
|
|
|
711
|
+
/**
|
|
712
|
+
* Waits for the manifest to be loaded (or to fail with an error).
|
|
713
|
+
* @return {!Promise}
|
|
714
|
+
*/
|
|
715
|
+
waitForManifest() {
|
|
716
|
+
const promises = [
|
|
717
|
+
this.manifestPromise_,
|
|
718
|
+
this.successPromise_,
|
|
719
|
+
];
|
|
720
|
+
return Promise.race(promises);
|
|
721
|
+
}
|
|
722
|
+
|
|
703
723
|
/**
|
|
704
724
|
* Releases or stops all non-entrusted resources.
|
|
705
725
|
*
|
|
@@ -82,7 +82,7 @@ shaka.media.StreamingEngine = class {
|
|
|
82
82
|
this.deferredCloseSegmentIndex_ = new Map();
|
|
83
83
|
|
|
84
84
|
/** @private {number} */
|
|
85
|
-
this.
|
|
85
|
+
this.bufferingScale_ = 1;
|
|
86
86
|
|
|
87
87
|
/** @private {?shaka.extern.Variant} */
|
|
88
88
|
this.currentVariant_ = null;
|
|
@@ -148,6 +148,9 @@ shaka.media.StreamingEngine = class {
|
|
|
148
148
|
|
|
149
149
|
/** @private {number} */
|
|
150
150
|
this.playRangeEnd_ = Infinity;
|
|
151
|
+
|
|
152
|
+
/** @private {?shaka.media.StreamingEngine.MediaState_} */
|
|
153
|
+
this.lastTextMediaStateBeforeUnload_ = null;
|
|
151
154
|
}
|
|
152
155
|
|
|
153
156
|
/** @override */
|
|
@@ -380,6 +383,8 @@ shaka.media.StreamingEngine = class {
|
|
|
380
383
|
if (state) {
|
|
381
384
|
this.cancelUpdate_(state);
|
|
382
385
|
this.abortOperations_(state).catch(() => {});
|
|
386
|
+
this.lastTextMediaStateBeforeUnload_ =
|
|
387
|
+
this.mediaStates_.get(ContentType.TEXT);
|
|
383
388
|
this.mediaStates_.delete(ContentType.TEXT);
|
|
384
389
|
}
|
|
385
390
|
this.currentTextStream_ = null;
|
|
@@ -476,6 +481,7 @@ shaka.media.StreamingEngine = class {
|
|
|
476
481
|
* @param {shaka.extern.Stream} textStream
|
|
477
482
|
*/
|
|
478
483
|
async switchTextStream(textStream) {
|
|
484
|
+
this.lastTextMediaStateBeforeUnload_ = null;
|
|
479
485
|
this.currentTextStream_ = textStream;
|
|
480
486
|
|
|
481
487
|
if (!this.startupComplete_) {
|
|
@@ -1357,7 +1363,7 @@ shaka.media.StreamingEngine = class {
|
|
|
1357
1363
|
this.config_.bufferingGoal);
|
|
1358
1364
|
|
|
1359
1365
|
const scaledBufferingGoal = Math.max(1,
|
|
1360
|
-
unscaledBufferingGoal * this.
|
|
1366
|
+
unscaledBufferingGoal * this.bufferingScale_);
|
|
1361
1367
|
|
|
1362
1368
|
// Check if we've buffered to the end of the presentation.
|
|
1363
1369
|
const timeUntilEnd =
|
|
@@ -1800,7 +1806,7 @@ shaka.media.StreamingEngine = class {
|
|
|
1800
1806
|
}
|
|
1801
1807
|
this.mediaStates_.delete(ContentType.TEXT);
|
|
1802
1808
|
} else if (error.code == shaka.util.Error.Code.QUOTA_EXCEEDED_ERROR) {
|
|
1803
|
-
this.handleQuotaExceeded_(mediaState, error);
|
|
1809
|
+
await this.handleQuotaExceeded_(mediaState, error);
|
|
1804
1810
|
} else {
|
|
1805
1811
|
shaka.log.error(logPrefix, 'failed fetch and append: code=' +
|
|
1806
1812
|
error.code);
|
|
@@ -1871,9 +1877,10 @@ shaka.media.StreamingEngine = class {
|
|
|
1871
1877
|
*
|
|
1872
1878
|
* @param {shaka.media.StreamingEngine.MediaState_} mediaState
|
|
1873
1879
|
* @param {!shaka.util.Error} error
|
|
1880
|
+
* @return {!Promise}
|
|
1874
1881
|
* @private
|
|
1875
1882
|
*/
|
|
1876
|
-
handleQuotaExceeded_(mediaState, error) {
|
|
1883
|
+
async handleQuotaExceeded_(mediaState, error) {
|
|
1877
1884
|
const logPrefix = shaka.media.StreamingEngine.logPrefix_(mediaState);
|
|
1878
1885
|
|
|
1879
1886
|
// The segment cannot fit into the SourceBuffer. Ideally, MediaSource would
|
|
@@ -1895,18 +1902,22 @@ shaka.media.StreamingEngine = class {
|
|
|
1895
1902
|
});
|
|
1896
1903
|
|
|
1897
1904
|
if (!waitingForAnotherStreamToRecover) {
|
|
1905
|
+
const maxDisabledTime = this.getDisabledTime_(error);
|
|
1906
|
+
if (maxDisabledTime) {
|
|
1907
|
+
shaka.log.debug(logPrefix, 'Disabling stream due to quota', error);
|
|
1908
|
+
}
|
|
1898
1909
|
const handled = this.playerInterface_.disableStream(
|
|
1899
|
-
mediaState.stream,
|
|
1910
|
+
mediaState.stream, maxDisabledTime);
|
|
1900
1911
|
if (handled) {
|
|
1901
1912
|
return;
|
|
1902
1913
|
}
|
|
1903
1914
|
// Reduction schedule: 80%, 60%, 40%, 20%, 16%, 12%, 8%, 4%, fail.
|
|
1904
1915
|
// Note: percentages are used for comparisons to avoid rounding errors.
|
|
1905
|
-
const percentBefore = Math.round(100 * this.
|
|
1916
|
+
const percentBefore = Math.round(100 * this.bufferingScale_);
|
|
1906
1917
|
if (percentBefore > 20) {
|
|
1907
|
-
this.
|
|
1918
|
+
this.bufferingScale_ -= 0.2;
|
|
1908
1919
|
} else if (percentBefore > 4) {
|
|
1909
|
-
this.
|
|
1920
|
+
this.bufferingScale_ -= 0.04;
|
|
1910
1921
|
} else {
|
|
1911
1922
|
shaka.log.error(
|
|
1912
1923
|
logPrefix, 'MediaSource threw QuotaExceededError too many times');
|
|
@@ -1915,12 +1926,14 @@ shaka.media.StreamingEngine = class {
|
|
|
1915
1926
|
this.playerInterface_.onError(error);
|
|
1916
1927
|
return;
|
|
1917
1928
|
}
|
|
1918
|
-
const percentAfter = Math.round(100 * this.
|
|
1929
|
+
const percentAfter = Math.round(100 * this.bufferingScale_);
|
|
1919
1930
|
shaka.log.warning(
|
|
1920
1931
|
logPrefix,
|
|
1921
1932
|
'MediaSource threw QuotaExceededError:',
|
|
1922
1933
|
'reducing buffering goals by ' + (100 - percentAfter) + '%');
|
|
1923
1934
|
mediaState.recovering = true;
|
|
1935
|
+
const presentationTime = this.playerInterface_.getPresentationTime();
|
|
1936
|
+
await this.evict_(mediaState, presentationTime);
|
|
1924
1937
|
} else {
|
|
1925
1938
|
shaka.log.debug(
|
|
1926
1939
|
logPrefix,
|
|
@@ -2521,12 +2534,16 @@ shaka.media.StreamingEngine = class {
|
|
|
2521
2534
|
// Use the max segment duration, if it is longer than the bufferBehind, to
|
|
2522
2535
|
// avoid accidentally clearing too much data when dealing with a manifest
|
|
2523
2536
|
// with a long keyframe interval.
|
|
2524
|
-
const bufferBehind = Math.max(
|
|
2537
|
+
const bufferBehind = Math.max(
|
|
2538
|
+
this.config_.bufferBehind * this.bufferingScale_,
|
|
2525
2539
|
this.manifest_.presentationTimeline.getMaxSegmentDuration());
|
|
2526
2540
|
|
|
2527
2541
|
const startTime =
|
|
2528
2542
|
this.playerInterface_.mediaSourceEngine.bufferStart(mediaState.type);
|
|
2529
2543
|
if (startTime == null) {
|
|
2544
|
+
if (this.lastTextMediaStateBeforeUnload_ == mediaState) {
|
|
2545
|
+
this.lastTextMediaStateBeforeUnload_ = null;
|
|
2546
|
+
}
|
|
2530
2547
|
shaka.log.v2(logPrefix,
|
|
2531
2548
|
'buffer behind okay because nothing buffered:',
|
|
2532
2549
|
'presentationTime=' + presentationTime,
|
|
@@ -2561,6 +2578,11 @@ shaka.media.StreamingEngine = class {
|
|
|
2561
2578
|
|
|
2562
2579
|
this.destroyer_.ensureNotDestroyed();
|
|
2563
2580
|
shaka.log.v1(logPrefix, 'evicted ' + overflow + ' seconds');
|
|
2581
|
+
|
|
2582
|
+
if (this.lastTextMediaStateBeforeUnload_) {
|
|
2583
|
+
await this.evict_(this.lastTextMediaStateBeforeUnload_, presentationTime);
|
|
2584
|
+
this.destroyer_.ensureNotDestroyed();
|
|
2585
|
+
}
|
|
2564
2586
|
}
|
|
2565
2587
|
|
|
2566
2588
|
|
|
@@ -2806,18 +2828,26 @@ shaka.media.StreamingEngine = class {
|
|
|
2806
2828
|
* @private
|
|
2807
2829
|
*/
|
|
2808
2830
|
async handleStreamingError_(mediaState, error) {
|
|
2831
|
+
const logPrefix = shaka.media.StreamingEngine.logPrefix_(mediaState);
|
|
2832
|
+
|
|
2809
2833
|
// If we invoke the callback right away, the application could trigger a
|
|
2810
2834
|
// rapid retry cycle that could be very unkind to the server. Instead,
|
|
2811
2835
|
// use the backoff system to delay and backoff the error handling.
|
|
2812
2836
|
await this.failureCallbackBackoff_.attempt();
|
|
2813
2837
|
this.destroyer_.ensureNotDestroyed();
|
|
2814
|
-
|
|
2815
|
-
|
|
2838
|
+
|
|
2839
|
+
// Try to recover from network errors, but not timeouts.
|
|
2840
|
+
// See https://github.com/shaka-project/shaka-player/issues/7368
|
|
2841
|
+
if (error.category === shaka.util.Error.Category.NETWORK &&
|
|
2842
|
+
error.code != shaka.util.Error.Code.TIMEOUT) {
|
|
2816
2843
|
if (mediaState.restoreStreamAfterTrickPlay) {
|
|
2817
2844
|
this.setTrickPlay(/* on= */ false);
|
|
2818
2845
|
return;
|
|
2819
2846
|
}
|
|
2820
2847
|
const maxDisabledTime = this.getDisabledTime_(error);
|
|
2848
|
+
if (maxDisabledTime) {
|
|
2849
|
+
shaka.log.debug(logPrefix, 'Disabling stream due to error', error);
|
|
2850
|
+
}
|
|
2821
2851
|
error.handled = this.playerInterface_.disableStream(
|
|
2822
2852
|
mediaState.stream, maxDisabledTime);
|
|
2823
2853
|
|
|
@@ -2842,6 +2872,7 @@ shaka.media.StreamingEngine = class {
|
|
|
2842
2872
|
|
|
2843
2873
|
/**
|
|
2844
2874
|
* @param {!shaka.util.Error} error
|
|
2875
|
+
* @return {number}
|
|
2845
2876
|
* @private
|
|
2846
2877
|
*/
|
|
2847
2878
|
getDisabledTime_(error) {
|
package/lib/player.js
CHANGED
|
@@ -1421,13 +1421,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1421
1421
|
//
|
|
1422
1422
|
// DrmEngine requires this to be done before we destroy DrmEngine itself.
|
|
1423
1423
|
if (this.video_ && this.video_.src) {
|
|
1424
|
-
// TODO: Investigate this more. Only reproduces on Firefox 69.
|
|
1425
|
-
// Introduce a delay before detaching the video source. We are seeing
|
|
1426
|
-
// spurious Promise rejections involving an AbortError in our tests
|
|
1427
|
-
// otherwise.
|
|
1428
|
-
await new Promise(
|
|
1429
|
-
(resolve) => new shaka.util.Timer(resolve).tickAfter(0.1));
|
|
1430
|
-
|
|
1431
1424
|
this.video_.removeAttribute('src');
|
|
1432
1425
|
this.video_.load();
|
|
1433
1426
|
}
|
|
@@ -1664,6 +1657,16 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1664
1657
|
await this.srcEqualsInner_(startTimeOfLoad, mimeType);
|
|
1665
1658
|
}, 'srcEqualsInner_');
|
|
1666
1659
|
} else {
|
|
1660
|
+
// Wait for the manifest to be parsed.
|
|
1661
|
+
await mutexWrapOperation(async () => {
|
|
1662
|
+
await preloadManager.waitForManifest();
|
|
1663
|
+
// Retrieve the manifest. This is specifically put before the media
|
|
1664
|
+
// source engine is initialized, for the benefit of event handlers.
|
|
1665
|
+
this.parserFactory_ = preloadManager.getParserFactory();
|
|
1666
|
+
this.parser_ = preloadManager.receiveParser();
|
|
1667
|
+
this.manifest_ = preloadManager.getManifest();
|
|
1668
|
+
}, 'waitForFinish');
|
|
1669
|
+
|
|
1667
1670
|
if (!this.mediaSourceEngine_) {
|
|
1668
1671
|
await mutexWrapOperation(async () => {
|
|
1669
1672
|
await this.initializeMediaSourceEngineInner_();
|
|
@@ -1678,14 +1681,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1678
1681
|
// Get manifest and associated values from preloader.
|
|
1679
1682
|
this.config_ = preloadManager.getConfiguration();
|
|
1680
1683
|
this.manifestFilterer_ = preloadManager.getManifestFilterer();
|
|
1681
|
-
this.parserFactory_ = preloadManager.getParserFactory();
|
|
1682
|
-
this.parser_ = preloadManager.receiveParser();
|
|
1683
1684
|
if (this.parser_ && this.parser_.setMediaElement && this.video_) {
|
|
1684
1685
|
this.parser_.setMediaElement(this.video_);
|
|
1685
1686
|
}
|
|
1686
1687
|
this.regionTimeline_ = preloadManager.receiveRegionTimeline();
|
|
1687
1688
|
this.qualityObserver_ = preloadManager.getQualityObserver();
|
|
1688
|
-
this.manifest_ = preloadManager.getManifest();
|
|
1689
1689
|
const currentAdaptationSetCriteria =
|
|
1690
1690
|
preloadManager.getCurrentAdaptationSetCriteria();
|
|
1691
1691
|
if (currentAdaptationSetCriteria) {
|
|
@@ -7859,7 +7859,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
|
|
|
7859
7859
|
* @export
|
|
7860
7860
|
*/
|
|
7861
7861
|
// eslint-disable-next-line no-useless-concat, max-len
|
|
7862
|
-
shaka.Player.version = 'v4.10.
|
|
7862
|
+
shaka.Player.version = 'v4.10.21' + '-uncompiled'; // x-release-please-version
|
|
7863
7863
|
|
|
7864
7864
|
// Initialize the deprecation system using the version string we just set
|
|
7865
7865
|
// on the player.
|
|
@@ -10,6 +10,7 @@ goog.require('shaka.log');
|
|
|
10
10
|
goog.require('shaka.media.Capabilities');
|
|
11
11
|
goog.require('shaka.polyfill');
|
|
12
12
|
goog.require('shaka.util.DrmUtils');
|
|
13
|
+
goog.require('shaka.util.MimeUtils');
|
|
13
14
|
goog.require('shaka.util.Platform');
|
|
14
15
|
|
|
15
16
|
|
|
@@ -222,6 +223,7 @@ shaka.polyfill.MediaCapabilities = class {
|
|
|
222
223
|
* @private
|
|
223
224
|
*/
|
|
224
225
|
static async checkDrmSupport_(videoConfig, audioConfig, mcapKeySystemConfig) {
|
|
226
|
+
const MimeUtils = shaka.util.MimeUtils;
|
|
225
227
|
const audioCapabilities = [];
|
|
226
228
|
const videoCapabilities = [];
|
|
227
229
|
|
|
@@ -277,8 +279,12 @@ shaka.polyfill.MediaCapabilities = class {
|
|
|
277
279
|
mediaKeySystemConfig.videoCapabilities = videoCapabilities;
|
|
278
280
|
}
|
|
279
281
|
|
|
280
|
-
const
|
|
281
|
-
const
|
|
282
|
+
const videoMimeType = videoConfig ? videoConfig.contentType : '';
|
|
283
|
+
const audioMimeType = audioConfig ? audioConfig.contentType : '';
|
|
284
|
+
const videoCodec = MimeUtils.getBasicType(videoMimeType) + ';' +
|
|
285
|
+
MimeUtils.getCodecBase(videoMimeType);
|
|
286
|
+
const audioCodec = MimeUtils.getBasicType(audioMimeType) + ';' +
|
|
287
|
+
MimeUtils.getCodecBase(audioMimeType);
|
|
282
288
|
const keySystem = mcapKeySystemConfig.keySystem;
|
|
283
289
|
|
|
284
290
|
/** @type {MediaKeySystemAccess} */
|
package/lib/util/periods.js
CHANGED
package/package.json
CHANGED
package/ui/locales/zh-HK.json
CHANGED
|
@@ -1,30 +1,49 @@
|
|
|
1
1
|
{
|
|
2
|
+
"AD_DURATION": "廣告長度",
|
|
3
|
+
"AD_PROGRESS": "廣告 [AD_ON]/[NUM_ADS]",
|
|
4
|
+
"AD_STATISTICS": "廣告統計資料",
|
|
5
|
+
"AD_TIME": "廣告:[AD_TIME]",
|
|
6
|
+
"AIRPLAY": "AirPlay",
|
|
2
7
|
"AUTO_QUALITY": "自動",
|
|
3
8
|
"BACK": "返回",
|
|
4
9
|
"CAPTIONS": "字幕",
|
|
5
10
|
"CAST": "投放…",
|
|
6
|
-
"
|
|
7
|
-
"
|
|
11
|
+
"CHAPTERS": "章節",
|
|
12
|
+
"DOWNLOAD_VIDEO_FRAME": "儲存影片影格",
|
|
13
|
+
"ENTER_LOOP_MODE": "循環播放目前的影片",
|
|
14
|
+
"ENTER_PICTURE_IN_PICTURE": "進入子母畫面",
|
|
15
|
+
"EXIT_FULL_SCREEN": "退出全螢幕模式",
|
|
16
|
+
"EXIT_LOOP_MODE": "停止循環播放目前的影片",
|
|
8
17
|
"EXIT_PICTURE_IN_PICTURE": "退出畫中畫",
|
|
9
18
|
"FAST_FORWARD": "快轉",
|
|
10
19
|
"FULL_SCREEN": "全螢幕",
|
|
11
20
|
"LANGUAGE": "語言",
|
|
12
21
|
"LIVE": "直播",
|
|
13
|
-
"
|
|
22
|
+
"LOOP": "循環播放",
|
|
23
|
+
"MORE_SETTINGS": "顯示更多設定",
|
|
14
24
|
"MULTIPLE_LANGUAGES": "多種語言",
|
|
15
|
-
"MUTE": "
|
|
25
|
+
"MUTE": "静音",
|
|
16
26
|
"NOT_APPLICABLE": "不適用",
|
|
17
|
-
"OFF": "
|
|
18
|
-
"ON": "",
|
|
27
|
+
"OFF": "關閉",
|
|
28
|
+
"ON": "開啟",
|
|
19
29
|
"PAUSE": "暫停",
|
|
20
30
|
"PICTURE_IN_PICTURE": "畫中畫",
|
|
21
31
|
"PLAY": "播放",
|
|
22
|
-
"
|
|
32
|
+
"PLAYBACK_RATE": "播放速度",
|
|
33
|
+
"QUALITY": "音質",
|
|
34
|
+
"RECENTER_VR": "重新置中",
|
|
35
|
+
"REPLAY": "重播",
|
|
36
|
+
"RESOLUTION": "解析度",
|
|
23
37
|
"REWIND": "倒帶",
|
|
24
|
-
"SEEK": "
|
|
25
|
-
"
|
|
38
|
+
"SEEK": "進度",
|
|
39
|
+
"SKIP_AD": "略過廣告",
|
|
40
|
+
"SKIP_TO_LIVE": "跳至当前直播",
|
|
41
|
+
"STATISTICS": "統計資料",
|
|
42
|
+
"SUBTITLE_FORCED": "強制",
|
|
43
|
+
"SURROUND": "環迴音效",
|
|
44
|
+
"TOGGLE_STEREOSCOPIC": "切換立體視覺",
|
|
26
45
|
"UNDETERMINED_LANGUAGE": "不明",
|
|
27
|
-
"UNMUTE": "
|
|
28
|
-
"UNRECOGNIZED_LANGUAGE": "
|
|
46
|
+
"UNMUTE": "取消靜音",
|
|
47
|
+
"UNRECOGNIZED_LANGUAGE": "無法辨識",
|
|
29
48
|
"VOLUME": "音量"
|
|
30
49
|
}
|