Skip to content

Commit c0e6322

Browse files
CHaNGeTeavelad
authored andcommitted
fix: Timeout unfulfilled request to decodingInfo and requestMediaKeySystemAccess (#7682)
On some (Android) WebView environments, decodingInfo and requestMediaKeySystemAccess will not resolve or reject, at least if RESOURCE_PROTECTED_MEDIA_ID is not set. This is a workaround for that issue. Closes #7680
1 parent 2280965 commit c0e6322

File tree

4 files changed

+92
-7
lines changed

4 files changed

+92
-7
lines changed

lib/media/drm_engine.js

+23-6
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ goog.require('shaka.util.DrmUtils');
1515
goog.require('shaka.util.Error');
1616
goog.require('shaka.util.EventManager');
1717
goog.require('shaka.util.FakeEvent');
18+
goog.require('shaka.util.Functional');
1819
goog.require('shaka.util.IDestroyable');
1920
goog.require('shaka.util.Iterables');
2021
goog.require('shaka.util.ManifestParserUtils');
@@ -1859,9 +1860,16 @@ shaka.media.DrmEngine = class {
18591860
offlineConfig.sessionTypes = ['persistent-license'];
18601861

18611862
const configs = [offlineConfig, basicConfig];
1862-
1863-
const access = await navigator.requestMediaKeySystemAccess(
1864-
keySystem, configs);
1863+
// On some (Android) WebView environments,
1864+
// requestMediaKeySystemAccess will
1865+
// not resolve or reject, at least if RESOURCE_PROTECTED_MEDIA_ID
1866+
// is not set. This is a workaround for that issue.
1867+
const TIMEOUT_FOR_CHECKACCESS_IN_SECONDS = 1;
1868+
const access =
1869+
await shaka.util.Functional.promiseWithTimeout(
1870+
TIMEOUT_FOR_CHECKACCESS_IN_SECONDS,
1871+
navigator.requestMediaKeySystemAccess(keySystem, configs),
1872+
);
18651873
await processMediaKeySystemAccess(keySystem, access);
18661874
} catch (error) {} // Ignore errors.
18671875
};
@@ -1896,13 +1904,22 @@ shaka.media.DrmEngine = class {
18961904
},
18971905
},
18981906
};
1899-
1907+
// On some (Android) WebView environments, decodingInfo will
1908+
// not resolve or reject, at least if RESOURCE_PROTECTED_MEDIA_ID
1909+
// is not set. This is a workaround for that issue.
1910+
const TIMEOUT_FOR_DECODING_INFO_IN_SECONDS = 1;
19001911
const decodingInfo =
1901-
await navigator.mediaCapabilities.decodingInfo(decodingConfig);
1912+
await shaka.util.Functional.promiseWithTimeout(
1913+
TIMEOUT_FOR_DECODING_INFO_IN_SECONDS,
1914+
navigator.mediaCapabilities.decodingInfo(decodingConfig),
1915+
);
19021916

19031917
const access = decodingInfo.keySystemAccess;
19041918
await processMediaKeySystemAccess(keySystem, access);
1905-
} catch (error) {} // Ignore errors.
1919+
} catch (error) {
1920+
// Ignore errors.
1921+
shaka.log.v2('Failed to probe support for', keySystem, error);
1922+
}
19061923
};
19071924

19081925
// Initialize the support structure for each key system.

lib/util/functional.js

+27
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
goog.provide('shaka.util.Functional');
88

9+
goog.require('shaka.util.Timer');
10+
911
/**
1012
* @summary A set of functional utility functions.
1113
*/
@@ -67,4 +69,29 @@ shaka.util.Functional = class {
6769
static isNotNull(value) {
6870
return value != null;
6971
}
72+
73+
/**
74+
* Returns a Promise which is resolved only if |asyncProcess| is resolved, and
75+
* only if it is resolved in less than |seconds| seconds.
76+
*
77+
* If the returned Promise is resolved, it returns the same value as
78+
* |asyncProcess|.
79+
*
80+
* If |asyncProcess| fails, the returned Promise is rejected.
81+
* If |asyncProcess| takes too long, the returned Promise is rejected, but
82+
* |asyncProcess| is still allowed to complete.
83+
*
84+
* @param {number} seconds
85+
* @param {!Promise.<T>} asyncProcess
86+
* @return {!Promise.<T>}
87+
* @template T
88+
*/
89+
static promiseWithTimeout(seconds, asyncProcess) {
90+
return Promise.race([
91+
asyncProcess,
92+
new Promise(((_, reject) => {
93+
new shaka.util.Timer(reject).tickAfter(seconds);
94+
})),
95+
]);
96+
}
7097
};

lib/util/stream_utils.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -727,7 +727,14 @@ shaka.util.StreamUtils = class {
727727
mimeType, audioCodec);
728728
}
729729
promises.push(new Promise((resolve, reject) => {
730-
navigator.mediaCapabilities.decodingInfo(copy).then((res) => {
730+
// On some (Android) WebView environments, decodingInfo will
731+
// not resolve or reject, at least if RESOURCE_PROTECTED_MEDIA_ID
732+
// is not set. This is a workaround for that issue.
733+
const TIMEOUT_FOR_DECODING_INFO_IN_SECONDS = 1;
734+
shaka.util.Functional.promiseWithTimeout(
735+
TIMEOUT_FOR_DECODING_INFO_IN_SECONDS,
736+
navigator.mediaCapabilities.decodingInfo(copy),
737+
).then((res) => {
731738
resolve(res);
732739
}).catch(reject);
733740
}));

test/util/functional_unit.js

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*! @license
2+
* Shaka Player
3+
* Copyright 2016 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
describe('Functional', () => {
8+
const Functional = shaka.util.Functional;
9+
describe('promiseWithTimeout', () => {
10+
it('resolves if asyncProcess resolves within the timeout', async () => {
11+
const asyncProcess = new Promise((resolve) =>
12+
setTimeout(() => resolve('success'), 100),
13+
);
14+
const result = await Functional.promiseWithTimeout(1, asyncProcess);
15+
expect(result).toBe('success');
16+
});
17+
18+
it('rejects if asyncProcess rejects', async () => {
19+
const asyncProcess = new Promise((_, reject) =>
20+
setTimeout(() => reject('error'), 100),
21+
);
22+
const promise = Functional.promiseWithTimeout(1, asyncProcess);
23+
await expectAsync(promise).toBeRejectedWith('error');
24+
});
25+
26+
it('rejects if asyncProcess takes longer than the timeout', async () => {
27+
const asyncProcess = new Promise((resolve) =>
28+
setTimeout(() => resolve('success'), 2000),
29+
);
30+
const promise = Functional.promiseWithTimeout(1, asyncProcess);
31+
await expectAsync(promise).toBeRejected();
32+
});
33+
});
34+
});

0 commit comments

Comments
 (0)