diff --git a/examples/vanilla/favicon/favicon-256x256.png b/examples/vanilla/favicon/favicon-256x256.png
new file mode 100644
index 000000000..2f84287e1
Binary files /dev/null and b/examples/vanilla/favicon/favicon-256x256.png differ
diff --git a/examples/vanilla/favicon/favicon-32x32.png b/examples/vanilla/favicon/favicon-32x32.png
new file mode 100644
index 000000000..1815eecc3
Binary files /dev/null and b/examples/vanilla/favicon/favicon-32x32.png differ
diff --git a/examples/vanilla/favicon/favicon-48x48.png b/examples/vanilla/favicon/favicon-48x48.png
new file mode 100644
index 000000000..13202ae3a
Binary files /dev/null and b/examples/vanilla/favicon/favicon-48x48.png differ
diff --git a/examples/vanilla/index.html b/examples/vanilla/index.html
index 43a3afc30..4df7ef6ba 100644
--- a/examples/vanilla/index.html
+++ b/examples/vanilla/index.html
@@ -20,6 +20,7 @@
Getting started
Animated icons
Understanding <media-controller/>
slots
State change events demo
+ Progressive Web Application (PWA)
Responsive controls
diff --git a/examples/vanilla/pwa/index.html b/examples/vanilla/pwa/index.html
new file mode 100644
index 000000000..dbafdc5c0
--- /dev/null
+++ b/examples/vanilla/pwa/index.html
@@ -0,0 +1,111 @@
+
+
+
+
+ Media Chrome Basic PWA Example
+
+
+
+
+
+
+ Media Chrome Basic PWA Example
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This is an example Progressive Web App. If you need help on how to
+ install, see
+
here.
+
+
+
+
diff --git a/examples/vanilla/pwa/pwa-manifest.json b/examples/vanilla/pwa/pwa-manifest.json
new file mode 100644
index 000000000..185524b92
--- /dev/null
+++ b/examples/vanilla/pwa/pwa-manifest.json
@@ -0,0 +1,25 @@
+{
+ "name": "Media Chrome",
+ "short_name": "MediaChrome",
+ "start_url": ".",
+ "display": "standalone",
+ "background_color": "#fff",
+ "description": "A readable Hacker News app.",
+ "icons": [
+ {
+ "src": "/examples/vanilla/favicon/favicon-32x32.png",
+ "sizes": "32x32",
+ "type": "image/png"
+ },
+ {
+ "src": "/examples/vanilla/favicon/favicon-48x48.png",
+ "sizes": "48x48",
+ "type": "image/png"
+ },
+ {
+ "src": "/examples/vanilla/favicon/favicon-256x256.png",
+ "sizes": "256x256",
+ "type": "image/png"
+ }
+ ]
+ }
diff --git a/src/js/utils/platform-tests.js b/src/js/utils/platform-tests.js
index ade158e6b..6cccc9da1 100644
--- a/src/js/utils/platform-tests.js
+++ b/src/js/utils/platform-tests.js
@@ -14,7 +14,7 @@ export const getTestMediaEl = () => {
/**
* Test for volume support
- *
+ *
* @param {HTMLMediaElement} mediaEl
* @returns {Promise}
*/
@@ -32,18 +32,27 @@ export const hasVolumeSupportAsync = async (mediaEl = getTestMediaEl()) => {
// return volumeSupported;
// });
+// NOTE: This also matches at least some non-Safari UAs on e.g. iOS, such as Chrome, perhaps since
+// these browsers are built on top of the OS-level WebKit browser, so use accordingly (CJP).
+// See, e.g.: https://www.whatismybrowser.com/guides/the-latest-user-agent/chrome
+const isSafari = /.*Version\/.*Safari\/.*/.test(globalThis.navigator.userAgent);
/**
* Test for PIP support
- *
+ *
* @param {HTMLVideoElement} mediaEl
* @returns {boolean}
*/
-export const hasPipSupport = (mediaEl = getTestMediaEl()) =>
- typeof mediaEl?.requestPictureInPicture === 'function';
+export const hasPipSupport = (mediaEl = getTestMediaEl()) => {
+ // NOTE: PWAs for Apple that rely on Safari don't support picture in picture but still have `requestPictureInPicture()`
+ // (which will result in a failed promise). Checking for those conditions here (CJP).
+ // This should still work for macOS PWAs installed using Chrome, where PiP is supported.
+ if (globalThis.matchMedia('(display-mode: standalone)').matches && isSafari) return false;
+ return typeof mediaEl?.requestPictureInPicture === 'function';
+}
/**
* Test for Fullscreen support
- *
+ *
* @param {HTMLVideoElement} mediaEl
* @returns {boolean}
*/
diff --git a/src/js/utils/server-safe-globals.js b/src/js/utils/server-safe-globals.js
index 9544852b5..937f4c966 100644
--- a/src/js/utils/server-safe-globals.js
+++ b/src/js/utils/server-safe-globals.js
@@ -36,7 +36,19 @@ const globalThisShim = {
CustomEvent: function CustomEvent() {},
getComputedStyle: function () {},
navigator: {
- languages: []
+ languages: [],
+ get userAgent() {
+ return '';
+ }
+ },
+ /**
+ * @param {string} media
+ */
+ matchMedia(media) {
+ return {
+ matches: false,
+ media,
+ };
}
};
@@ -70,6 +82,7 @@ const isShimmed = Object.keys(globalThisShim)
* ResizeObserver?,
* CastableVideoElement?,
* navigator?,
+ * matchMedia,
* } }
* */
export const GlobalThis = isServer && !isShimmed ? globalThisShim : globalThis;