diff --git a/build/conformance.textproto b/build/conformance.textproto
index bcbf158ecb..7c9407efc6 100644
--- a/build/conformance.textproto
+++ b/build/conformance.textproto
@@ -333,3 +333,25 @@ requirement: {
"See also https://bit.ly/3wAsoj5"
whitelist_regexp: "node_modules/"
}
+
+# Disallow the general use of TextDecoder and TextEncoder, which is not
+# available on all supported platforms.
+requirement: {
+ type: BANNED_NAME_CALL
+ value: "TextDecoder"
+ value: "window.TextDecoder"
+ error_message:
+ "Using \"TextDecoder\" directly is not allowed; "
+ "because is not supported on Xbox and old browsers."
+ whitelist_regexp: "lib/util/string_utils.js"
+}
+
+requirement: {
+ type: BANNED_NAME_CALL
+ value: "TextEncoder"
+ value: "window.TextEncoder"
+ error_message:
+ "Using \"TextEncoder\" directly is not allowed; "
+ "because is not supported on Xbox and old browsers."
+ whitelist_regexp: "lib/util/string_utils.js"
+}
diff --git a/demo/index.html b/demo/index.html
index c376c27d50..409c16cb82 100644
--- a/demo/index.html
+++ b/demo/index.html
@@ -50,8 +50,6 @@
-
-
diff --git a/docs/tutorials/upgrade.md b/docs/tutorials/upgrade.md
index 800bffbcaa..3aefa2bd9d 100644
--- a/docs/tutorials/upgrade.md
+++ b/docs/tutorials/upgrade.md
@@ -25,6 +25,7 @@ application:
- TextDecoder/TextEncoder platform support or polyfill required (affects
Xbox One, but not evergreen browsers); we suggest the polyfill
[https://github.com/anonyco/FastestSmallestTextEncoderDecoder](fastestsmallesttextencoderdecoder/EncoderDecoderTogether.min.js)
+ Fallback included by default in v4.2
- Support removed:
- IE11 support removed
diff --git a/lib/util/string_utils.js b/lib/util/string_utils.js
index 895fd08358..61ad403c68 100644
--- a/lib/util/string_utils.js
+++ b/lib/util/string_utils.js
@@ -36,19 +36,38 @@ shaka.util.StringUtils = class {
if (uint8[0] == 0xef && uint8[1] == 0xbb && uint8[2] == 0xbf) {
uint8 = uint8.subarray(3);
}
-
- // Use the TextDecoder interface to decode the text. This has the advantage
- // compared to the previously-standard decodeUriComponent that it will
- // continue parsing even if it finds an invalid UTF8 character, rather than
- // stop and throw an error.
- const utf8decoder = new TextDecoder();
- const decoded = utf8decoder.decode(uint8);
- if (decoded.includes('\uFFFD')) {
- shaka.log.alwaysError('Decoded string contains an "unknown character" ' +
- 'codepoint. That probably means the UTF8 ' +
- 'encoding was incorrect!');
+ if (window.TextDecoder) {
+ // Use the TextDecoder interface to decode the text. This has the
+ // advantage compared to the previously-standard decodeUriComponent that
+ // it will continue parsing even if it finds an invalid UTF8 character,
+ // rather than stop and throw an error.
+ const utf8decoder = new TextDecoder();
+ const decoded = utf8decoder.decode(uint8);
+ if (decoded.includes('\uFFFD')) {
+ shaka.log.alwaysError('Decoded string contains an "unknown character' +
+ '" codepoint. That probably means the UTF8 ' +
+ 'encoding was incorrect!');
+ }
+ return decoded;
+ } else {
+ // http://stackoverflow.com/a/13691499
+ const utf8 = shaka.util.StringUtils.fromCharCode(uint8);
+ // This converts each character in the string to an escape sequence. If
+ // the character is in the ASCII range, it is not converted; otherwise it
+ // is converted to a URI escape sequence.
+ // Example: '\x67\x35\xe3\x82\xac' -> 'g#%E3%82%AC'
+ const escaped = escape(utf8);
+ // Decode the escaped sequence. This will interpret UTF-8 sequences into
+ // the correct character.
+ // Example: 'g#%E3%82%AC' -> 'g#€'
+ try {
+ return decodeURIComponent(escaped);
+ } catch (e) {
+ throw new shaka.util.Error(
+ shaka.util.Error.Severity.CRITICAL, shaka.util.Error.Category.TEXT,
+ shaka.util.Error.Code.BAD_ENCODING);
+ }
}
- return decoded;
}
@@ -141,8 +160,30 @@ shaka.util.StringUtils = class {
* @export
*/
static toUTF8(str) {
- const utf8Encoder = new TextEncoder();
- return shaka.util.BufferUtils.toArrayBuffer(utf8Encoder.encode(str));
+ if (window.TextEncoder) {
+ const utf8Encoder = new TextEncoder();
+ return shaka.util.BufferUtils.toArrayBuffer(utf8Encoder.encode(str));
+ } else {
+ // http://stackoverflow.com/a/13691499
+ // Converts the given string to a URI encoded string. If a character
+ // falls in the ASCII range, it is not converted; otherwise it will be
+ // converted to a series of URI escape sequences according to UTF-8.
+ // Example: 'g#€' -> 'g#%E3%82%AC'
+ const encoded = encodeURIComponent(str);
+ // Convert each escape sequence individually into a character. Each
+ // escape sequence is interpreted as a code-point, so if an escape
+ // sequence happens to be part of a multi-byte sequence, each byte will
+ // be converted to a single character.
+ // Example: 'g#%E3%82%AC' -> '\x67\x35\xe3\x82\xac'
+ const utf8 = unescape(encoded);
+
+ const result = new Uint8Array(utf8.length);
+ for (let i = 0; i < utf8.length; i++) {
+ const item = utf8[i];
+ result[i] = item.charCodeAt(0);
+ }
+ return shaka.util.BufferUtils.toArrayBuffer(result);
+ }
}
diff --git a/package-lock.json b/package-lock.json
index 0c7a9b7dc3..4e731425e2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -28,7 +28,6 @@
"eslint-config-google": "^0.14.0",
"eslint-plugin-shaka-rules": "file:./build/eslint-plugin-shaka-rules",
"esprima": "^4.0.1",
- "fastestsmallesttextencoderdecoder": "^1.0.22",
"fontfaceonload": "^1.0.2",
"google-closure-compiler-java": "^20220301.0.0",
"google-closure-deps": "https://gitpkg.now.sh/google/closure-library/closure-deps?d7736da6",
@@ -4137,12 +4136,6 @@
"integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==",
"dev": true
},
- "node_modules/fastestsmallesttextencoderdecoder": {
- "version": "1.0.22",
- "resolved": "https://registry.npmjs.org/fastestsmallesttextencoderdecoder/-/fastestsmallesttextencoderdecoder-1.0.22.tgz",
- "integrity": "sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw==",
- "dev": true
- },
"node_modules/fastq": {
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz",
@@ -11643,12 +11636,6 @@
"integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==",
"dev": true
},
- "fastestsmallesttextencoderdecoder": {
- "version": "1.0.22",
- "resolved": "https://registry.npmjs.org/fastestsmallesttextencoderdecoder/-/fastestsmallesttextencoderdecoder-1.0.22.tgz",
- "integrity": "sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw==",
- "dev": true
- },
"fastq": {
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz",
diff --git a/package.json b/package.json
index 405401d709..53a8e0b732 100644
--- a/package.json
+++ b/package.json
@@ -27,7 +27,6 @@
"eslint-config-google": "^0.14.0",
"eslint-plugin-shaka-rules": "file:./build/eslint-plugin-shaka-rules",
"esprima": "^4.0.1",
- "fastestsmallesttextencoderdecoder": "^1.0.22",
"fontfaceonload": "^1.0.2",
"google-closure-compiler-java": "^20220301.0.0",
"google-closure-deps": "https://gitpkg.now.sh/google/closure-library/closure-deps?d7736da6",
@@ -71,7 +70,6 @@
"dialog-polyfill/dist/dialog-polyfill.js",
"eme-encryption-scheme-polyfill/index.js",
"es6-promise-polyfill/promise.min.js",
- "fastestsmallesttextencoderdecoder/EncoderDecoderTogether.min.js",
"google-closure-library/closure/goog/base.js",
"less/dist/less.js",
"material-design-lite/dist/material.indigo-blue.min.css",