@mux/mux-player
Advanced tools
Comparing version 0.1.0-canary.10-2039108 to 0.1.0-canary.10-2a741f0
@@ -6,2 +6,42 @@ # Change Log | ||
# [0.1.0-beta.26](https://github.com/muxinc/elements/compare/@mux/mux-player@0.1.0-beta.25...@mux/mux-player@0.1.0-beta.26) (2022-08-03) | ||
### Bug Fixes | ||
- update stream-type values in warning log ([d995694](https://github.com/muxinc/elements/commit/d995694966d877097c63e23c7425097f5c50e259)) | ||
# [0.1.0-beta.25](https://github.com/muxinc/elements/compare/@mux/mux-player@0.1.0-beta.24...@mux/mux-player@0.1.0-beta.25) (2022-08-02) | ||
### Bug Fixes | ||
- move nohotkeys type to more appropriate place ([49f9c4e](https://github.com/muxinc/elements/commit/49f9c4e4d43f463aa4960a40c94e715d86c4304b)) | ||
- **mux-player:** Account for attr vs. 'prop' naming overlap in state propogation. ([5c05af8](https://github.com/muxinc/elements/commit/5c05af8c257806662bc6402baba03b7090cbe699)) | ||
### Features | ||
- add CSS parts for controls ([#310](https://github.com/muxinc/elements/issues/310)) ([e28c71e](https://github.com/muxinc/elements/commit/e28c71eed11423951dbac9faf2518ca7cbb2f9e2)) | ||
- nohotkeys prop, only use the template ([4cde791](https://github.com/muxinc/elements/commit/4cde791a4664b11501ad48125bd5ed80e3970ff0)) | ||
- support media-chrome keyboard shortcuts, use nohotkeys to turn off ([b8ed7f5](https://github.com/muxinc/elements/commit/b8ed7f5180aab60bb896842fb7037bce0069ad2c)) | ||
- upgrade to media-chrome 0.9.0 ([f257e0d](https://github.com/muxinc/elements/commit/f257e0d6583de19d0f29859b512e12654f235f3a)) | ||
# [0.1.0-beta.24](https://github.com/muxinc/elements/compare/@mux/mux-player@0.1.0-beta.23...@mux/mux-player@0.1.0-beta.24) (2022-07-21) | ||
### Bug Fixes | ||
- add Mux flavor cast icon & fix xs size bug ([#299](https://github.com/muxinc/elements/issues/299)) ([8374ff1](https://github.com/muxinc/elements/commit/8374ff17e294cbe3ad899749ab0361b78ffe0274)) | ||
- **mux-player:** Clean up how metadata is set from mux-player to underlying mux-video element. Force upgrades and init when setting metadata. ([d3b2347](https://github.com/muxinc/elements/commit/d3b2347b1d4d7ebbbd882879f69e9d082f12dd80)) | ||
- upgrade MC to v0.8.0 and adjust time range styles ([#294](https://github.com/muxinc/elements/issues/294)) ([030fdc6](https://github.com/muxinc/elements/commit/030fdc649517a538fc89c5b3d9edca42e58634ef)) | ||
- upgrade MC v0.8.1 ([#298](https://github.com/muxinc/elements/issues/298)) ([fc4a74c](https://github.com/muxinc/elements/commit/fc4a74cfaadf90ee8d4ed89751755d56e77df79e)) | ||
### Features | ||
- add ability to choose a Media Theme via attribute ([#269](https://github.com/muxinc/elements/issues/269)) ([77d0386](https://github.com/muxinc/elements/commit/77d0386606d5ecccb7c322ce487c9287d16374fd)) | ||
- add defaultMuted, defaultPlaybackRate props ([#252](https://github.com/muxinc/elements/issues/252)) ([1a72165](https://github.com/muxinc/elements/commit/1a7216545cba27b34bc743cf5dd6225d4dcae738)) | ||
- **mux-player:** Add textTracks prop alongside add/remove tracks methods. ([c041a72](https://github.com/muxinc/elements/commit/c041a72ce414fc52fcd90e22cc7730ef2e349c31)) | ||
- **playback-core, mux-player:** Add support to removeTextTrack. Remove all identified tracks on hls destroy. Add methods to mux-player. ([d090b06](https://github.com/muxinc/elements/commit/d090b060a8b8b3772e74762176af9881299bf894)) | ||
# [0.1.0-beta.23](https://github.com/muxinc/elements/compare/@mux/mux-player@0.1.0-beta.22...@mux/mux-player@0.1.0-beta.23) (2022-07-11) | ||
**Note:** Version bump only for package @mux/mux-player | ||
# [0.1.0-beta.22](https://github.com/muxinc/elements/compare/@mux/mux-player@0.1.0-beta.21...@mux/mux-player@0.1.0-beta.22) (2022-07-05) | ||
@@ -8,0 +48,0 @@ |
{ | ||
"name": "@mux/mux-player", | ||
"version": "0.1.0-canary.10-2039108", | ||
"version": "0.1.0-canary.10-2a741f0", | ||
"description": "An open source Mux player web component that Just Works™", | ||
@@ -54,5 +54,5 @@ "main": "./dist/index.cjs.js", | ||
"@github/template-parts": "^0.5.3", | ||
"@mux/mux-video": "0.8.1", | ||
"@mux/playback-core": "0.8.0", | ||
"media-chrome": "0.6.9" | ||
"@mux/mux-video": "0.8.5-canary.1-2a741f0", | ||
"@mux/playback-core": "0.9.1-canary.10-2a741f0", | ||
"media-chrome": "0.9.0" | ||
}, | ||
@@ -59,0 +59,0 @@ "devDependencies": { |
@@ -81,3 +81,2 @@ <p align="center"> | ||
stream-type="on-demand" | ||
controls | ||
></mux-player> | ||
@@ -99,3 +98,2 @@ </body> | ||
metadata-viewer-user-id="user-id-1234" | ||
controls | ||
> | ||
@@ -136,3 +134,2 @@ </mux-player> | ||
prefer-mse | ||
controls | ||
> | ||
@@ -144,30 +141,32 @@ </mux-player> | ||
| Attribute | Type | Description | Default | | ||
| -------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------- | | ||
| `playback-id` | `string` | The playback ID for your Mux Asset or Mux Live Stream. This will also be used for automatically assigning a [poster image](https://docs.mux.com/guides/video/get-images-from-a-video) and (thumbnail previews)[https://docs.mux.com/guides/video/create-timeline-hover-previews]. For more, check out the [Mux Docs](https://docs.mux.com/guides/video/play-your-videos#1-get-your-playback-id). | N/A | | ||
| `env-key` | `string` | Your Mux Data environment key. Note that this is different than your API Key. Get your env key from the "Mux Data" part of your [Mux Environments Dashboard](https://dashboard.mux.com/environments). If undefined, the environment will be inferred based on your Mux Video asset. | `undefined` | | ||
| `playback-token` | `string` | The playback token for signing the `src` URL. | N/A | | ||
| `thumbnail-token` | `string` | The thumbnail token for signing the `poster` URL. | N/A | | ||
| `storyboard-token` | `string` | The storyboard token for signing the storyboard URL. | N/A | | ||
| `stream-type` | `"on-demand" \| "live" \| "ll-live" \| "dvr" \| "ll-dvr"` | The type of stream associated with your Mux Asset. Used to determine what UI/controls to show and what optimizations to make for playback. | `"on-demand"` | | ||
| `audio` | `boolean` | Indicate that you want an "audio only" UI/chrome. This may be used for audio-only assets or audio+video assets. | `false` | | ||
| `metadata-video-title` | `string` | This is an arbitrary title for your video that will be passed in as metadata into Mux Data. Adding a title will give you useful context in your Mux Data dashboard. (optional, but encouraged) | N/A | | ||
| `metadata-viewer-user-id` | `string` | If you have a logged-in user this should be an anonymized ID value that maps back to the user in your database. Take care to not expose personal identifiable information like names, usernames or email addresses. (optional, but encouraged) | N/A | | ||
| `metadata-video-id` | `string` | This is an arbitrary ID that should map back to a record of this video in your database. | N/A | | ||
| `debug` | `boolean` | Enables debug mode for the underlying playback engine (currently hls.js) and mux-embed, providing additional information in the console. | `false` | | ||
| `start-time` | `number` (seconds) | Specify where in the media's timeline you want playback to start. | `0` | | ||
| `thumbnail-time` | `number` (seconds) | Offset for the poster image you want to show before loading media. If no `thumbnail-time` is specified, `start-time` will be used by default. NOTE: This feature currently cannot be used with `thumbnail-token`. | `0` | | ||
| `prefer-mse` | `boolean` | Use the underlying playback engine (currently hls.js), even if native playback is supported (e.g. in Safari). For more, see the section on [`prefer-mse`](#prefer-mse) | `false` | | ||
| `default-hidden-captions` | `boolean` | Hide captions by default instead of showing them on initial load (when available) | `false` | | ||
| `forward-seek-offset` | `number` (seconds) | Offset applied to the forward seek button | `10` | | ||
| `backward-seek-offset` | `number` (seconds) | Offset applied to the backward seek button | `10` | | ||
| `primary-color` | (Any valid CSS color style) | The primary color used by the player | N/A | | ||
| `secondary-color` | (Any valid CSS color style) | The secondary color used by the player | N/A | | ||
| `volume` | `number` (0-1) | Sets the volume of the player from 0 to 1. | Varies | | ||
| `muted` | `boolean` | Toggles the muted state of the player. | Varies | | ||
| `autoplay` | `boolean` | Toggles whether or not media should auto-play when initially loaded | false | | ||
| `playback-rate` | `number` | Applies a multiplier to the media's playback rate, either speeding it up or slowing it down. | `1` | | ||
| `loop` | `boolean` | Automatically loop playback of your media when it finishes. | `false` | | ||
| `poster` | `string` (URL) | Assigns a poster image URL. Will use the automatically generated poster based on your playback-id by default. | Derived | | ||
| `beacon-collection-domain` | `string` (domain name) | Assigns a custom domain to be used for Mux Data collection. | N/A | | ||
| Attribute | Type | Description | Default | | ||
| -------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------- | | ||
| `playback-id` | `string` | The playback ID for your Mux Asset or Mux Live Stream. This will also be used for automatically assigning a [poster image](https://docs.mux.com/guides/video/get-images-from-a-video) and (thumbnail previews)[https://docs.mux.com/guides/video/create-timeline-hover-previews]. For more, check out the [Mux Docs](https://docs.mux.com/guides/video/play-your-videos#1-get-your-playback-id). | N/A | | ||
| `env-key` | `string` | Your Mux Data environment key. Note that this is different than your API Key. Get your env key from the "Mux Data" part of your [Mux Environments Dashboard](https://dashboard.mux.com/environments). If undefined, the environment will be inferred based on your Mux Video asset. | `undefined` | | ||
| `playback-token` | `string` | The playback token for signing the `src` URL. | N/A | | ||
| `thumbnail-token` | `string` | The thumbnail token for signing the `poster` URL. | N/A | | ||
| `storyboard-token` | `string` | The storyboard token for signing the storyboard URL. | N/A | | ||
| `stream-type` | `"on-demand" \| "live" \| "ll-live" \| "live:dvr" \| "ll-live:dvr"` | The type of stream associated with your Mux Asset. Used to determine what UI/controls to show and what optimizations to make for playback. | `"on-demand"` | | ||
| `audio` | `boolean` | Indicate that you want an "audio only" UI/chrome. This may be used for audio-only assets or audio+video assets. | `false` | | ||
| `metadata-video-title` | `string` | This is an arbitrary title for your video that will be passed in as metadata into Mux Data. Adding a title will give you useful context in your Mux Data dashboard. (optional, but encouraged) | N/A | | ||
| `metadata-viewer-user-id` | `string` | If you have a logged-in user this should be an anonymized ID value that maps back to the user in your database. Take care to not expose personal identifiable information like names, usernames or email addresses. (optional, but encouraged) | N/A | | ||
| `metadata-video-id` | `string` | This is an arbitrary ID that should map back to a record of this video in your database. | N/A | | ||
| `debug` | `boolean` | Enables debug mode for the underlying playback engine (currently hls.js) and mux-embed, providing additional information in the console. | `false` | | ||
| `start-time` | `number` (seconds) | Specify where in the media's timeline you want playback to start. | `0` | | ||
| `thumbnail-time` | `number` (seconds) | Offset for the poster image you want to show before loading media. If no `thumbnail-time` is specified, `start-time` will be used by default. NOTE: This feature currently cannot be used with `thumbnail-token`. | `0` | | ||
| `prefer-mse` | `boolean` | Use the underlying playback engine (currently hls.js), even if native playback is supported (e.g. in Safari). For more, see the section on [`prefer-mse`](#prefer-mse) | `false` | | ||
| `default-hidden-captions` | `boolean` | Hide captions by default instead of showing them on initial load (when available) | `false` | | ||
| `forward-seek-offset` | `number` (seconds) | Offset applied to the forward seek button | `10` | | ||
| `backward-seek-offset` | `number` (seconds) | Offset applied to the backward seek button | `10` | | ||
| `primary-color` | (Any valid CSS color style) | The primary color used by the player | N/A | | ||
| `secondary-color` | (Any valid CSS color style) | The secondary color used by the player | N/A | | ||
| `volume` | `number` (0-1) | Sets the volume of the player from 0 to 1. | Varies | | ||
| `muted` | `boolean` | Toggles the muted state of the player. | Varies | | ||
| `autoplay` | `boolean` | Toggles whether or not media should auto-play when initially loaded | false | | ||
| `playbackrate` | `number` | Applies a multiplier to the media's playback rate, either speeding it up or slowing it down. | `1` | | ||
| `loop` | `boolean` | Automatically loop playback of your media when it finishes. | `false` | | ||
| `poster` | `string` (URL) | Assigns a poster image URL. Will use the automatically generated poster based on your playback-id by default. | Derived | | ||
| `beacon-collection-domain` | `string` (domain name) | Assigns a custom domain to be used for Mux Data collection. | N/A | | ||
| `custom-domain` | `string` (domain name) | Assigns a custom domain to be used for Mux Video. | N/A | | ||
| `nohotkeys` | `boolean` | Toggles keyboard shortcut (hot keys) support when focus in inside the player | `false` | | ||
@@ -185,10 +184,12 @@ ### Methods | ||
| --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | | ||
| `autoplay` | A `Boolean` that reflects the autoplay HTML attribute, indicating whether playback should automatically begin as soon as enough media is available to do so without interruption. | `false` | | ||
| `autoplay` | A `Boolean` that reflects the `autoplay` HTML attribute, indicating whether playback should automatically begin as soon as enough media is available to do so without interruption. | `false` | | ||
| `buffered` <sub><sup>Read only</sup></sub> | Returns a `TimeRanges` object that indicates the ranges of the media source that the browser has buffered (if any) at the moment the buffered property is accessed. | `undefined` | | ||
| `controls` | Is a Boolean that reflects the controls HTML attribute, indicating whether user interface items for controlling the resource should be displayed. | `false` | | ||
| `crossOrigin` | A DOMString indicating the CORS setting for this media element. | `null` | | ||
| `currentTime` | A double-precision floating-point value indicating the current playback time in seconds; if the media has not started to play and has not been seeked, this value is the media's initial playback time. Setting this value seeks the media to the new time. The time is specified relative to the media's timeline. | `0` | | ||
| `defaultMuted` | Is a `Boolean` that reflects the `muted` HTML attribute, determines whether audio is muted by default. `true` if the audio output should be muted by default and `false` otherwise. | `false` | | ||
| `defaultPlaybackRate` | Is a double that reflects the `playbackrate` HTML attribute, it indicates the default playback rate for the media. | `1` | | ||
| `duration` <sub><sup>Read only</sup></sub> | A read-only double-precision floating-point value indicating the total duration of the media in seconds. If no media data is available, the returned value is `NaN`. If the media is of indefinite length (such as streamed live media, a WebRTC call's media, or similar), the value is `+Infinity`. | `NaN` | | ||
| `ended` <sub><sup>Read only</sup></sub> | Returns a `Boolean` that indicates whether the media element has finished playing. | `false` | | ||
| `loop` | A `Boolean` that reflects the loop HTML attribute, which indicates whether the media element should start over when it reaches the end. | `false` | | ||
| `loop` | A `Boolean` that reflects the `loop` HTML attribute, which indicates whether the media element should start over when it reaches the end. | `false` | | ||
| `nohotkeys` | A `Boolean` that reflects the `nohotkeys` HTML attribute, which indicates whether Mux Player accepts keboard shortcuts. | `false` | | ||
| `metadata` | The metadata property can be used to set the Mux Data metadata properties in an easy way. Take a look at the [metadata guide](https://docs.mux.com/guides/data/make-your-data-actionable-with-metadata) to view an exhaustive list of available values. | `{}` | | ||
@@ -199,4 +200,4 @@ | `muted` | Is a `Boolean` that determines whether audio is muted. `true` if the audio is muted and `false` otherwise. | `false` | | ||
| `playsInline` | A Boolean attribute indicating that the video is to be played "inline", that is within the element's playback area. Note that the absence of this attribute does not imply that the video will always be played in fullscreen. | `false` | | ||
| `preload` | Is a `DOMString` that reflects the preload HTML attribute, indicating what data should be preloaded, if any. Possible values are: `none`, `metadata`, `auto`. | `undefined` | | ||
| `src` | Is a `String` that reflects the src HTML attribute, which contains the URL of a media resource to use. | `undefined` | | ||
| `preload` | Is a `DOMString` that reflects the `preload` HTML attribute, indicating what data should be preloaded, if any. Possible values are: `none`, `metadata`, `auto`. | `undefined` | | ||
| `src` | Is a `String` that reflects the `src` HTML attribute, which contains the URL of a media resource to use. | `undefined` | | ||
| `startTime` | `Number` that specifies where in the media's timeline you want playback to start (in seconds). | `0` | | ||
@@ -208,2 +209,3 @@ | `thumbnailTime` | `Number` offset (in seconds) for the poster image you want to show before loading media. If no `thumbnailTime` is specified, `startTime` will be used by default. NOTE: This feature currently cannot be used with `tokens.thumbnail`. | `0` | | ||
| `volume` | Is a double indicating the audio volume, from 0.0 (silent) to 1.0 (loudest). | `1` | | ||
| `customDomain` | Is a `String` that assigns a custom domain to be used for Mux Video. | `undefined` | | ||
@@ -210,0 +212,0 @@ ### Events |
@@ -59,2 +59,9 @@ import { toQuery, camelCase } from './utils'; | ||
export function castThemeName(themeName?: string): string | undefined { | ||
if (themeName && /^media-theme-[\w-]+$/.test(themeName)) { | ||
return themeName; | ||
} | ||
return undefined; | ||
} | ||
const attrToPropNameMap: Record<string, string> = { | ||
@@ -69,26 +76,2 @@ crossorigin: 'crossOrigin', | ||
let testMediaEl: HTMLMediaElement | undefined; | ||
export const getTestMediaEl = (nodeName = 'video') => { | ||
if (testMediaEl) return testMediaEl; | ||
if (typeof window !== 'undefined') { | ||
testMediaEl = document.createElement(nodeName as 'video' | 'audio'); | ||
} | ||
return testMediaEl; | ||
}; | ||
export const hasVolumeSupportAsync = async (mediaEl: HTMLMediaElement | undefined = getTestMediaEl()) => { | ||
if (!mediaEl) return false; | ||
const prevVolume = mediaEl.volume; | ||
mediaEl.volume = prevVolume / 2 + 0.1; | ||
return new Promise<boolean>((resolve, reject) => { | ||
setTimeout(() => { | ||
resolve(mediaEl.volume !== prevVolume); | ||
}, 0); | ||
}); | ||
}; | ||
export function getCcSubTracks(el: MuxPlayerElement) { | ||
return Array.from(el.media?.textTracks ?? []).filter(({ kind }) => kind === 'subtitles' || kind === 'captions'); | ||
} | ||
export const getLiveTime = (el: MuxPlayerElement) => { | ||
@@ -95,0 +78,0 @@ const { media } = el; |
@@ -152,4 +152,45 @@ import { TemplateInstance, NodeTemplatePart, createProcessor, AttributeTemplatePart } from '@github/template-parts'; | ||
const stringsCache = new Map(); | ||
const defaultProcessor = createProcessor(processPart); | ||
export function html(strings: TemplateStringsArray, ...values: unknown[]): TemplateResult { | ||
const staticStrings: any = ['']; | ||
const dynamicValues: any[] = []; | ||
let staticValues; | ||
let hasStatics = false; | ||
// Here the unsafe static values are moved from the string expressions | ||
// to the static strings so they can be used in the cache key and later | ||
// be used to generate the HTML via the <template> element. | ||
const join = (strs: TemplateStringsArray, vals: any[] = []) => { | ||
staticStrings[staticStrings.length - 1] = staticStrings[staticStrings.length - 1] + strs[0]; | ||
vals.forEach((dynamicValue, i) => { | ||
if ((staticValues = dynamicValue?.$static$) !== undefined) { | ||
staticValues.forEach((staticValue: TemplateResult) => { | ||
join(staticValue.strings, staticValue.values); | ||
}); | ||
staticStrings[staticStrings.length - 1] = staticStrings[staticStrings.length - 1] + strs[i + 1]; | ||
hasStatics = true; | ||
} else { | ||
dynamicValues.push(dynamicValue); | ||
staticStrings.push(strs[i + 1]); | ||
} | ||
}); | ||
}; | ||
join(strings, values); | ||
if (hasStatics) { | ||
// Tagged template literals with the same static strings return the same | ||
// TemplateStringsArray, aka they are cached. emulate this behavior w/ a Map. | ||
const key = staticStrings.join('$$html$$'); | ||
strings = stringsCache.get(key); | ||
if (strings === undefined) { | ||
(staticStrings as any).raw = staticStrings; | ||
stringsCache.set(key, (strings = staticStrings)); | ||
} | ||
values = dynamicValues; | ||
} | ||
return new TemplateResult(strings, values, defaultProcessor); | ||
@@ -167,1 +208,10 @@ } | ||
} | ||
export const unsafeStatic = (...values: any[]) => ({ | ||
['$static$']: values.map((value) => { | ||
if (value instanceof TemplateResult) return value; | ||
// Only allow word characters and dashes for security. | ||
if (!/\w-/.test(value)) return { strings: [] }; | ||
return { strings: [value] }; | ||
}), | ||
}); |
103
src/index.ts
@@ -5,14 +5,6 @@ // import playback-core here to make sure that the polyfill is loaded | ||
import { MediaController } from 'media-chrome'; | ||
import MediaThemeMux from './media-theme-mux/media-theme-mux'; | ||
import MuxVideoElement, { MediaError } from '@mux/mux-video'; | ||
import { Metadata, StreamTypes } from '@mux/playback-core'; | ||
import { Metadata, StreamTypes, addTextTrack, removeTextTrack } from '@mux/playback-core'; | ||
import VideoApiElement, { initVideoApi } from './video-api'; | ||
import { | ||
getCcSubTracks, | ||
getPlayerVersion, | ||
hasVolumeSupportAsync, | ||
isInLiveWindow, | ||
seekToLive, | ||
toPropName, | ||
} from './helpers'; | ||
import { getPlayerVersion, isInLiveWindow, seekToLive, toPropName } from './helpers'; | ||
import { template } from './template'; | ||
@@ -32,4 +24,2 @@ import { render } from './html'; | ||
type MediaController = Element & { media: HTMLVideoElement }; | ||
const streamTypeValues = Object.values(StreamTypes); | ||
@@ -87,2 +77,3 @@ | ||
AUDIO: 'audio', | ||
NOHOTKEYS: 'nohotkeys', | ||
}; | ||
@@ -98,2 +89,3 @@ | ||
poster: el.getAttribute('poster'), | ||
theme: el.getAttribute('theme'), | ||
thumbnailTime: !el.tokens.thumbnail && el.thumbnailTime, | ||
@@ -103,2 +95,5 @@ autoplay: el.autoplay, | ||
loop: el.loop, | ||
// NOTE: Renaming internal prop due to state (sometimes derived from attributeChangedCallback attr values) | ||
// overwriting prop value (type mismatch: string vs. boolean) (CJP) | ||
noHotKeys: el.hasAttribute(PlayerAttributes.NOHOTKEYS), | ||
muted: el.muted, | ||
@@ -127,3 +122,2 @@ paused: el.paused, | ||
playerSize: getPlayerSize(el.mediaController ?? el), | ||
hasCaptions: !!getCcSubTracks(el).length, | ||
// NOTE: In order to guarantee all expected metadata props are set "from the outside" when used | ||
@@ -156,4 +150,2 @@ // and to guarantee they'll all be set *before* the playback id is set, using attr values here (CJP) | ||
...initialState, | ||
supportsAirPlay: false, | ||
supportsVolume: false, | ||
onCloseErrorDialog: () => this.#setState({ dialog: undefined, isDialogOpen: false }), | ||
@@ -195,15 +187,21 @@ onInitFocusDialog: (e) => { | ||
// even before they are connected to the main document. | ||
customElements.upgrade(this.theme as Node); | ||
if (!(this.theme instanceof MediaThemeMux)) { | ||
logger.error('<media-theme-mux> failed to upgrade!'); | ||
try { | ||
customElements.upgrade(this.theme as Node); | ||
if (!(this.theme instanceof HTMLElement)) throw ''; | ||
} catch (error) { | ||
logger.error(`<${this.theme?.localName}> failed to upgrade!`); | ||
} | ||
customElements.upgrade(this.media as Node); | ||
if (!(this.media instanceof MuxVideoElement)) { | ||
try { | ||
customElements.upgrade(this.media as Node); | ||
if (!(this.media instanceof MuxVideoElement)) throw ''; | ||
} catch (error) { | ||
logger.error('<mux-video> failed to upgrade!'); | ||
} | ||
customElements.upgrade(this.mediaController as Node); | ||
if (!(this.mediaController instanceof MediaController)) { | ||
logger.error('<media-controller> failed to upgrade!'); | ||
try { | ||
customElements.upgrade(this.mediaController as Node); | ||
if (!(this.mediaController instanceof MediaController)) throw ''; | ||
} catch (error) { | ||
logger.error(`<media-controller> failed to upgrade!`); | ||
} | ||
@@ -224,4 +222,2 @@ | ||
this.#setUpCaptionsButton(); | ||
this.#setUpAirplayButton(); | ||
this.#setUpVolumeRange(); | ||
this.#monitorLiveWindow(); | ||
@@ -504,18 +500,2 @@ this.#userInactive = this.mediaController?.hasAttribute('user-inactive') ?? true; | ||
#setUpAirplayButton() { | ||
if (!!(globalThis as any).WebKitPlaybackTargetAvailabilityEvent) { | ||
const onPlaybackTargetAvailability = (evt: any) => { | ||
const supportsAirPlay = evt.availability === 'available'; | ||
this.#setState({ supportsAirPlay }); | ||
}; | ||
this.media?.addEventListener('webkitplaybacktargetavailabilitychanged', onPlaybackTargetAvailability); | ||
} | ||
} | ||
async #setUpVolumeRange() { | ||
const supportsVolume = await hasVolumeSupportAsync(); | ||
this.#setState({ supportsVolume }); | ||
} | ||
attributeChangedCallback(attrName: string, oldValue: string | null, newValue: string) { | ||
@@ -586,3 +566,3 @@ if (!this.#isInit) { | ||
i18n( | ||
`No stream-type value supplied. Defaulting to \`on-demand\`. Please provide stream-type as either: \`on-demand\`, \`live\` or \`ll-live\`` | ||
`No stream-type value supplied. Defaulting to \`on-demand\`. Please provide stream-type as either: \`on-demand\`, \`live\`, \`ll-live\`, \`live:dvr\`, or \`ll-live:dvr\`` | ||
) | ||
@@ -595,3 +575,3 @@ ), | ||
message: i18n( | ||
`Invalid stream-type value supplied: \`{streamType}\`. Please provide stream-type as either: \`on-demand\`, \`live\` or \`ll-live\`` | ||
`Invalid stream-type value supplied: \`{streamType}\`. Please provide stream-type as either: \`on-demand\`, \`live\`, \`ll-live\`, \`live:dvr\`, or \`ll-live:dvr\`` | ||
).format({ streamType: this.streamType }), | ||
@@ -646,2 +626,13 @@ }); | ||
get nohotkeys() { | ||
return this.hasAttribute(PlayerAttributes.NOHOTKEYS); | ||
} | ||
set nohotkeys(val: boolean) { | ||
if (!val) { | ||
this.removeAttribute(PlayerAttributes.NOHOTKEYS); | ||
} | ||
this.setAttribute(PlayerAttributes.NOHOTKEYS, ''); | ||
} | ||
/** | ||
@@ -886,3 +877,11 @@ * Get the thumbnailTime offset used for the poster image. | ||
set metadata(val: Readonly<Metadata> | undefined) { | ||
if (this.media) this.media.metadata = val; | ||
if (!this.#isInit) { | ||
this.#init(); | ||
} | ||
// NOTE: This condition should never be met. If it is, there is a bug (CJP) | ||
if (!this.media) { | ||
logger.error('underlying media element missing when trying to set metadata. metadata will not be set.'); | ||
return; | ||
} | ||
this.media.metadata = val; | ||
} | ||
@@ -953,2 +952,18 @@ | ||
} | ||
addTextTrack(kind: TextTrackKind, label: string, lang?: string, id?: string) { | ||
const mediaEl = this.media?.nativeEl; | ||
if (!mediaEl) return; | ||
return addTextTrack(mediaEl, kind, label, lang, id); | ||
} | ||
removeTextTrack(track: TextTrack) { | ||
const mediaEl = this.media?.nativeEl; | ||
if (!mediaEl) return; | ||
return removeTextTrack(mediaEl, track); | ||
} | ||
get textTracks() { | ||
return this.media?.textTracks; | ||
} | ||
} | ||
@@ -955,0 +970,0 @@ |
@@ -6,2 +6,4 @@ // @ts-nocheck | ||
import captionsOnIcon from './icons/captions-on.svg'; | ||
import castEnter from './icons/cast-enter.svg'; | ||
import castExit from './icons/cast-exit.svg'; | ||
import fullscreenEnter from './icons/fullscreen-enter.svg'; | ||
@@ -23,2 +25,4 @@ import fullscreenExit from './icons/fullscreen-exit.svg'; | ||
export const CaptionsOn = () => createTemplateInstance(captionsOnIcon); | ||
export const CastEnter = () => createTemplateInstance(castEnter); | ||
export const CastExit = () => createTemplateInstance(castExit); | ||
export const FullscreenEnter = () => createTemplateInstance(fullscreenEnter); | ||
@@ -25,0 +29,0 @@ export const FullscreenExit = () => createTemplateInstance(fullscreenExit); |
@@ -1,2 +0,2 @@ | ||
import 'media-chrome'; | ||
import { MediaTheme } from 'media-chrome'; | ||
import { html, render } from '../html'; | ||
@@ -17,24 +17,85 @@ import '../media-chrome/time-display'; | ||
type ThemeMuxTemplateProps = { | ||
streamType: string; | ||
streamType: string | null; | ||
nohotkeys?: boolean; | ||
audio: boolean; | ||
playerSize: string; | ||
playerSize: string | null; | ||
defaultHiddenCaptions: boolean; | ||
hasCaptions: boolean; | ||
forwardSeekOffset: number; | ||
backwardSeekOffset: number; | ||
forwardSeekOffset: string | null; | ||
backwardSeekOffset: string | null; | ||
}; | ||
const template = (props: ThemeMuxTemplateProps) => html` | ||
<style> | ||
${cssStr} | ||
</style> | ||
export const parts = { | ||
mediaChrome: 'layer media-layer poster-layer vertical-layer centered-layer', | ||
centerPlay: 'center play button', | ||
centerSeekBackward: 'center seek-backward button', | ||
centerSeekForward: 'center seek-forward button', | ||
play: 'play button', | ||
seekBackward: 'seek-backward button', | ||
seekForward: 'seek-forward button', | ||
mute: 'mute button', | ||
captions: 'captions button', | ||
airplay: 'airplay button', | ||
pip: 'pip button', | ||
fullscreen: 'fullscreen button', | ||
cast: 'cast button', | ||
playbackRate: 'playbackrate button', | ||
volumeRange: 'volume range', | ||
timeRange: 'time range', | ||
timeDisplay: 'time display', | ||
}; | ||
<media-controller audio="${props.audio || false}" class="size-${props.playerSize}"> | ||
<slot name="media" slot="media"></slot> | ||
<media-loading-indicator slot="centered-chrome" no-auto-hide></media-loading-indicator> | ||
${ChromeRenderer(props)} | ||
<slot></slot> | ||
</media-controller> | ||
`; | ||
export default class MediaThemeMux extends MediaTheme { | ||
static get observedAttributes() { | ||
return [ | ||
'audio', | ||
'nohotkeys', | ||
'stream-type', | ||
'player-size', | ||
'default-hidden-captions', | ||
'forward-seek-offset', | ||
'backward-seek-offset', | ||
]; | ||
} | ||
attributeChangedCallback() { | ||
this.render(); | ||
} | ||
render() { | ||
const props = { | ||
audio: this.hasAttribute('audio'), | ||
nohotkeys: this.hasAttribute('nohotkeys'), | ||
streamType: this.getAttribute('stream-type'), | ||
playerSize: this.getAttribute('player-size'), | ||
defaultHiddenCaptions: this.hasAttribute('default-hidden-captions'), | ||
forwardSeekOffset: this.getAttribute('forward-seek-offset'), | ||
backwardSeekOffset: this.getAttribute('backward-seek-offset'), | ||
}; | ||
render( | ||
html` | ||
<style> | ||
${cssStr} | ||
</style> | ||
<media-controller | ||
nohotkeys="${props.nohotkeys || false}" | ||
audio="${props.audio || false}" | ||
class="size-${props.playerSize}" | ||
exportparts="${parts.mediaChrome.split(' ').join(', ')}" | ||
> | ||
<slot name="media" slot="media"></slot> | ||
<media-loading-indicator slot="centered-chrome" no-auto-hide></media-loading-indicator> | ||
${ChromeRenderer(props)} | ||
<slot></slot> | ||
</media-controller> | ||
`, | ||
this.shadowRoot as Node | ||
); | ||
} | ||
} | ||
if (!customElements.get('media-theme-mux')) { | ||
customElements.define('media-theme-mux', MediaThemeMux); | ||
} | ||
const ChromeRenderer = (props: ThemeMuxTemplateProps) => { | ||
@@ -46,3 +107,3 @@ const { streamType, playerSize, audio } = props; | ||
case StreamTypes.LL_LIVE: { | ||
return AudioLiveChrome(props); | ||
return AudioLiveChrome(); | ||
} | ||
@@ -99,4 +160,4 @@ case StreamTypes.DVR: | ||
// prettier-ignore | ||
const MediaPlayButton = () => html` | ||
<media-play-button> | ||
const MediaPlayButton = ({ part = parts.play } = {}) => html` | ||
<media-play-button part="${part}"> | ||
${icons.Play()} | ||
@@ -108,5 +169,6 @@ ${icons.Pause()} | ||
// prettier-ignore | ||
const MediaSeekBackwardButton = (props: Partial<ThemeMuxTemplateProps>) => html` | ||
<media-seek-backward-button seek-offset="${props.backwardSeekOffset}"> | ||
${icons.SeekBackward({ value: props.backwardSeekOffset })} | ||
type SeekBackwardButtonProps = { backwardSeekOffset: string, part?: string }; | ||
const MediaSeekBackwardButton = ({ backwardSeekOffset, part = parts.seekBackward }: SeekBackwardButtonProps) => html` | ||
<media-seek-backward-button seek-offset="${backwardSeekOffset}" part="${part}"> | ||
${icons.SeekBackward({ value: backwardSeekOffset })} | ||
</media-seek-backward-button> | ||
@@ -116,5 +178,6 @@ `; | ||
// prettier-ignore | ||
const MediaSeekForwardButton = (props: Partial<ThemeMuxTemplateProps>) => html` | ||
<media-seek-forward-button seek-offset="${props.forwardSeekOffset}"> | ||
${icons.SeekForward({ value: props.forwardSeekOffset })} | ||
type SeekForwardButtonProps = { forwardSeekOffset: string, part?: string }; | ||
const MediaSeekForwardButton = ({ forwardSeekOffset, part = parts.seekForward }: SeekForwardButtonProps) => html` | ||
<media-seek-forward-button seek-offset="${forwardSeekOffset}" part="${part}"> | ||
${icons.SeekForward({ value: forwardSeekOffset })} | ||
</media-seek-forward-button> | ||
@@ -125,3 +188,3 @@ `; | ||
const MediaMuteButton = () => html` | ||
<media-mute-button> | ||
<media-mute-button part="${parts.mute}"> | ||
${icons.VolumeHigh()} | ||
@@ -136,3 +199,3 @@ ${icons.VolumeMedium()} | ||
const MediaCaptionsButton = (props: ThemeMuxTemplateProps) => html` | ||
<media-captions-button default-showing="${!props.defaultHiddenCaptions}" > | ||
<media-captions-button default-showing="${!props.defaultHiddenCaptions}" part="${parts.captions}"> | ||
${icons.CaptionsOff()} | ||
@@ -144,3 +207,3 @@ ${icons.CaptionsOn()} | ||
const MediaAirplayButton = () => html` | ||
<media-airplay-button> | ||
<media-airplay-button part="${parts.airplay}"> | ||
${icons.Airplay()} | ||
@@ -151,3 +214,3 @@ </media-airplay-button>`; | ||
const MediaPipButton = () => html` | ||
<media-pip-button> | ||
<media-pip-button part="${parts.pip}"> | ||
${icons.PipEnter()} | ||
@@ -159,3 +222,3 @@ ${icons.PipExit()} | ||
const MediaFullscreenButton = () => html` | ||
<media-fullscreen-button> | ||
<media-fullscreen-button part="${parts.fullscreen}"> | ||
${icons.FullscreenEnter()} | ||
@@ -165,15 +228,46 @@ ${icons.FullscreenExit()} | ||
// prettier-ignore | ||
const MediaCastButton = () => html` | ||
<media-cast-button part="${parts.cast}"> | ||
${icons.CastEnter()} | ||
${icons.CastExit()} | ||
</media-cast-button>`; | ||
// prettier-ignore | ||
const MediaPlaybackRateButton = () => html` | ||
<media-playback-rate-button part="${parts.playbackRate}"> | ||
</media-playback-rate-button>`; | ||
// prettier-ignore | ||
const MediaVolumeRange = () => html` | ||
<media-volume-range part="${parts.volumeRange}"> | ||
</media-volume-range>`; | ||
// prettier-ignore | ||
const MediaTimeRange = () => html` | ||
<media-time-range part="${parts.timeRange}"> | ||
</media-time-range>`; | ||
// prettier-ignore | ||
const TimeDisplay = () => html` | ||
<mxp-time-display part="${parts.timeDisplay}"> | ||
</mxp-time-display>`; | ||
// prettier-ignore | ||
export const AudioVodChrome = (props: ThemeMuxTemplateProps) => html` | ||
<media-control-bar> | ||
${MediaPlayButton()} ${MediaSeekBackwardButton(props)} ${MediaSeekForwardButton(props)} | ||
<mxp-time-display></mxp-time-display> | ||
<media-time-range></media-time-range> | ||
${MediaPlayButton()} | ||
${MediaSeekBackwardButton(props as SeekBackwardButtonProps)} | ||
${MediaSeekForwardButton(props as SeekForwardButtonProps)} | ||
${TimeDisplay()} | ||
${MediaTimeRange()} | ||
${MediaMuteButton()} | ||
<media-volume-range></media-volume-range> | ||
<media-playback-rate-button></media-playback-rate-button> | ||
${MediaVolumeRange()} | ||
${MediaPlaybackRateButton()} | ||
${MediaAirplayButton()} | ||
<media-cast-button></media-cast-button> | ||
${MediaCastButton()} | ||
</media-control-bar> | ||
`; | ||
// prettier-ignore | ||
export const AudioDvrChrome = (props: ThemeMuxTemplateProps) => html` | ||
@@ -183,14 +277,16 @@ <media-control-bar> | ||
<slot name="seek-to-live-button"></slot> | ||
${MediaSeekBackwardButton(props)} ${MediaSeekForwardButton(props)} | ||
<mxp-time-display></mxp-time-display> | ||
<media-time-range></media-time-range> | ||
${MediaSeekBackwardButton(props as SeekBackwardButtonProps)} | ||
${MediaSeekForwardButton(props as SeekForwardButtonProps)} | ||
${TimeDisplay()} | ||
${MediaTimeRange()} | ||
${MediaMuteButton()} | ||
<media-volume-range></media-volume-range> | ||
<media-playback-rate-button></media-playback-rate-button> | ||
${MediaVolumeRange()} | ||
${MediaPlaybackRateButton()} | ||
${MediaAirplayButton()} | ||
<media-cast-button></media-cast-button> | ||
${MediaCastButton()} | ||
</media-control-bar> | ||
`; | ||
export const AudioLiveChrome = (_props: ThemeMuxTemplateProps) => html` | ||
// prettier-ignore | ||
export const AudioLiveChrome = () => html` | ||
<media-control-bar> | ||
@@ -200,5 +296,5 @@ ${MediaPlayButton()} | ||
${MediaMuteButton()} | ||
<media-volume-range></media-volume-range> | ||
${MediaVolumeRange()} | ||
${MediaAirplayButton()} | ||
<media-cast-button></media-cast-button> | ||
${MediaCastButton()} | ||
</media-control-bar> | ||
@@ -210,10 +306,10 @@ `; | ||
<media-control-bar slot="top-chrome"> | ||
${props.hasCaptions ? MediaCaptionsButton(props) : html``} | ||
${MediaCaptionsButton(props)} | ||
<div class="mxp-spacer"></div> | ||
${MediaAirplayButton()} | ||
<media-cast-button></media-cast-button> | ||
${MediaCastButton()} | ||
${MediaPipButton()} | ||
</media-control-bar> | ||
<div slot="centered-chrome" class="mxp-center-controls"> | ||
${MediaPlayButton()} | ||
${MediaPlayButton({ part: parts.centerPlay })} | ||
</div> | ||
@@ -230,19 +326,19 @@ <media-control-bar> | ||
<media-control-bar slot="top-chrome" style="justify-content: flex-end;"> | ||
${props.hasCaptions ? MediaCaptionsButton(props) : html``} | ||
${MediaCaptionsButton(props)} | ||
${MediaAirplayButton()} | ||
<media-cast-button></media-cast-button> | ||
${MediaCastButton()} | ||
${MediaPipButton()} | ||
</media-control-bar> | ||
<div slot="centered-chrome" class="mxp-center-controls"> | ||
${MediaSeekBackwardButton(props)} | ||
${MediaPlayButton()} | ||
${MediaSeekForwardButton(props)} | ||
${MediaSeekBackwardButton({ ...props, part: parts.centerSeekBackward } as SeekBackwardButtonProps)} | ||
${MediaPlayButton({ part: parts.centerPlay })} | ||
${MediaSeekForwardButton({ ...props, part: parts.centerSeekForward } as SeekForwardButtonProps)} | ||
</div> | ||
<media-time-range></media-time-range> | ||
${MediaTimeRange()} | ||
<media-control-bar> | ||
<mxp-time-display></mxp-time-display> | ||
${TimeDisplay()} | ||
${MediaMuteButton()} | ||
<media-volume-range></media-volume-range> | ||
${MediaVolumeRange()} | ||
<div class="mxp-spacer"></div> | ||
<media-playback-rate-button></media-playback-rate-button> | ||
${MediaPlaybackRateButton()} | ||
${MediaFullscreenButton()} | ||
@@ -256,17 +352,17 @@ <div class="mxp-padding-2"></div> | ||
<div slot="centered-chrome" class="mxp-center-controls"> | ||
${MediaPlayButton()} | ||
${MediaPlayButton({ part: parts.centerPlay })} | ||
</div> | ||
<media-time-range></media-time-range> | ||
${MediaTimeRange()} | ||
<media-control-bar> | ||
${MediaPlayButton()} | ||
${MediaSeekBackwardButton(props)} | ||
${MediaSeekForwardButton(props)} | ||
<mxp-time-display></mxp-time-display> | ||
${MediaSeekBackwardButton(props as SeekBackwardButtonProps)} | ||
${MediaSeekForwardButton(props as SeekForwardButtonProps)} | ||
${TimeDisplay()} | ||
${MediaMuteButton()} | ||
<media-volume-range></media-volume-range> | ||
${MediaVolumeRange()} | ||
<div class="mxp-spacer"></div> | ||
<media-playback-rate-button></media-playback-rate-button> | ||
${props.hasCaptions ? MediaCaptionsButton(props) : html``} | ||
${MediaPlaybackRateButton()} | ||
${MediaCaptionsButton(props)} | ||
${MediaAirplayButton()} | ||
<media-cast-button></media-cast-button> | ||
${MediaCastButton()} | ||
${MediaPipButton()} | ||
@@ -286,13 +382,13 @@ ${MediaFullscreenButton()} | ||
<div class="mxp-spacer"></div> | ||
${props.hasCaptions ? MediaCaptionsButton(props) : html``} | ||
${MediaCaptionsButton(props)} | ||
${MediaAirplayButton()} | ||
<media-cast-button></media-cast-button> | ||
${MediaCastButton()} | ||
${MediaPipButton()} | ||
</media-control-bar> | ||
<div slot="centered-chrome" class="mxp-center-controls"> | ||
${MediaPlayButton()} | ||
${MediaPlayButton({ part: parts.centerPlay })} | ||
</div> | ||
<media-control-bar> | ||
${MediaMuteButton()} | ||
<media-volume-range></media-volume-range> | ||
${MediaVolumeRange()} | ||
<div class="mxp-spacer"></div> | ||
@@ -309,11 +405,11 @@ ${MediaFullscreenButton()} | ||
<div slot="centered-chrome" class="mxp-center-controls"> | ||
${MediaPlayButton()} | ||
${MediaPlayButton({ part: parts.centerPlay })} | ||
</div> | ||
<media-control-bar> | ||
${MediaMuteButton()} | ||
<media-volume-range></media-volume-range> | ||
${MediaVolumeRange()} | ||
<div class="mxp-spacer"></div> | ||
${props.hasCaptions ? MediaCaptionsButton(props) : html``} | ||
${MediaCaptionsButton(props)} | ||
${MediaAirplayButton()} | ||
<media-cast-button></media-cast-button> | ||
${MediaCastButton()} | ||
${MediaPipButton()} | ||
@@ -330,16 +426,16 @@ ${MediaFullscreenButton()} | ||
<media-control-bar slot="top-chrome" style="justify-content: flex-end;"> | ||
${props.hasCaptions ? MediaCaptionsButton(props) : html``} | ||
${MediaCaptionsButton(props)} | ||
${MediaAirplayButton()} | ||
<media-cast-button></media-cast-button> | ||
${MediaCastButton()} | ||
${MediaPipButton()} | ||
</media-control-bar> | ||
<div slot="centered-chrome" class="mxp-center-controls"> | ||
${MediaSeekBackwardButton(props)} | ||
${MediaPlayButton()} | ||
${MediaSeekForwardButton(props)} | ||
${MediaSeekBackwardButton({ ...props, part: parts.centerSeekBackward } as SeekBackwardButtonProps)} | ||
${MediaPlayButton({ part: parts.centerPlay })} | ||
${MediaSeekForwardButton({ ...props, part: parts.centerSeekForward } as SeekForwardButtonProps)} | ||
</div> | ||
<media-time-range></media-time-range> | ||
${MediaTimeRange()} | ||
<media-control-bar> | ||
${MediaMuteButton()} | ||
<media-volume-range></media-volume-range> | ||
${MediaVolumeRange()} | ||
<slot name="seek-to-live-button"></slot> | ||
@@ -355,16 +451,16 @@ <div class="mxp-spacer"></div> | ||
<div slot="centered-chrome" class="mxp-center-controls"> | ||
${MediaPlayButton()} | ||
${MediaPlayButton({ part: parts.centerPlay })} | ||
</div> | ||
<media-time-range></media-time-range> | ||
${MediaTimeRange()} | ||
<media-control-bar> | ||
${MediaPlayButton()} | ||
${MediaSeekBackwardButton(props)} | ||
${MediaSeekForwardButton(props)} | ||
${MediaSeekBackwardButton(props as SeekBackwardButtonProps)} | ||
${MediaSeekForwardButton(props as SeekForwardButtonProps)} | ||
${MediaMuteButton()} | ||
<media-volume-range></media-volume-range> | ||
${MediaVolumeRange()} | ||
<slot name="seek-to-live-button"></slot> | ||
<div class="mxp-spacer"></div> | ||
${props.hasCaptions ? MediaCaptionsButton(props) : html``} | ||
${MediaCaptionsButton(props)} | ||
${MediaAirplayButton()} | ||
<media-cast-button></media-cast-button> | ||
${MediaCastButton()} | ||
${MediaPipButton()} | ||
@@ -375,45 +471,1 @@ ${MediaFullscreenButton()} | ||
`; | ||
function getProps(el: MediaThemeMux, state?: any): ThemeMuxTemplateProps { | ||
return { | ||
audio: el.hasAttribute('audio'), | ||
streamType: el.getAttribute('stream-type'), | ||
playerSize: el.getAttribute('player-size'), | ||
defaultHiddenCaptions: el.hasAttribute('default-hidden-captions'), | ||
hasCaptions: el.hasAttribute('has-captions'), | ||
forwardSeekOffset: el.getAttribute('forward-seek-offset'), | ||
backwardSeekOffset: el.getAttribute('backward-seek-offset'), | ||
...state, | ||
}; | ||
} | ||
class MediaThemeMux extends HTMLElement { | ||
static get observedAttributes() { | ||
return [ | ||
'audio', | ||
'stream-type', | ||
'player-size', | ||
'default-hidden-captions', | ||
'has-captions', | ||
'forward-seek-offset', | ||
'backward-seek-offset', | ||
]; | ||
} | ||
constructor() { | ||
super(); | ||
this.attachShadow({ mode: 'open' }); | ||
render(template(getProps(this)), this.shadowRoot as Node); | ||
} | ||
attributeChangedCallback() { | ||
render(template(getProps(this)), this.shadowRoot as Node); | ||
} | ||
} | ||
if (!customElements.get('media-theme-mux')) { | ||
customElements.define('media-theme-mux', MediaThemeMux); | ||
} | ||
export default MediaThemeMux; |
@@ -1,5 +0,10 @@ | ||
import './media-theme-mux/media-theme-mux'; | ||
import { parts } from './media-theme-mux/media-theme-mux'; | ||
import './dialog'; | ||
import { getSrcFromPlaybackId, getPosterURLFromPlaybackId, getStoryboardURLFromPlaybackId } from './helpers'; | ||
import { html } from './html'; | ||
import { | ||
castThemeName, | ||
getSrcFromPlaybackId, | ||
getPosterURLFromPlaybackId, | ||
getStoryboardURLFromPlaybackId, | ||
} from './helpers'; | ||
import { html, unsafeStatic } from './html'; | ||
// @ts-ignore | ||
@@ -19,18 +24,22 @@ import cssStr from './styles.css'; | ||
const flatCSSParts = Object.values(parts).flatMap((group) => group.split(' ')); | ||
const forwardUniqueCSSParts = [...new Set(flatCSSParts)].join(', '); | ||
export const content = (props: MuxTemplateProps) => html` | ||
<media-theme-mux | ||
<${unsafeStatic(castThemeName(props.theme) ?? 'media-theme-mux')} | ||
nohotkeys="${props.noHotKeys ?? false}" | ||
audio="${props.audio || false}" | ||
style="${stylePropsToString({ | ||
'--primary-color': props.primaryColor, | ||
'--secondary-color': props.secondaryColor, | ||
'--mux-time-range-padding': props.secondaryColor ? '0' : null, | ||
'--media-range-track-border-radius': props.secondaryColor ? '0' : null, | ||
}) ?? false}" | ||
class="size-${props.playerSize}" | ||
style="${ | ||
stylePropsToString({ | ||
'--primary-color': props.primaryColor, | ||
'--secondary-color': props.secondaryColor, | ||
}) ?? false | ||
}" | ||
class="size-${props.playerSize}${props.secondaryColor ? ' two-tone' : ''}" | ||
stream-type="${props.streamType}" | ||
player-size="${props.playerSize}" | ||
has-captions="${props.hasCaptions}" | ||
default-hidden-captions="${props.defaultHiddenCaptions}" | ||
forward-seek-offset="${props.forwardSeekOffset}" | ||
backward-seek-offset="${props.backwardSeekOffset}" | ||
exportparts="seek-live, ${forwardUniqueCSSParts}" | ||
> | ||
@@ -48,5 +57,5 @@ <mux-video | ||
start-time="${props.startTime != null ? props.startTime : false}" | ||
metadata-video-id="${props.metadataVideoId ?? props.metadata?.video_id ?? false}" | ||
metadata-video-title="${props.metadataVideoTitle ?? props.metadata?.video_title ?? false}" | ||
metadata-viewer-user-id="${props.metadataViewerUserId ?? props.metadata?.viewer_user_id ?? false}" | ||
metadata-video-id="${props.metadataVideoId ?? false}" | ||
metadata-video-title="${props.metadataVideoTitle ?? false}" | ||
metadata-viewer-user-id="${props.metadataViewerUserId ?? false}" | ||
beacon-collection-domain="${props.beaconCollectionDomain ?? false}" | ||
@@ -58,39 +67,49 @@ player-software-name="${props.playerSoftwareName}" | ||
custom-domain="${props.customDomain ?? false}" | ||
src="${!!props.src | ||
? props.src | ||
: props.playbackId | ||
? getSrcFromPlaybackId(props.playbackId, { domain: props.customDomain, token: props.tokens.playback }) | ||
: false}" | ||
poster="${!!props.poster | ||
? props.poster | ||
: props.playbackId && !props.audio | ||
? getPosterURLFromPlaybackId(props.playbackId, { | ||
domain: props.customDomain, | ||
thumbnailTime: props.thumbnailTime ?? props.startTime, | ||
token: props.tokens.thumbnail, | ||
}) | ||
: false}" | ||
cast-src="${!!props.src | ||
? props.src | ||
: props.playbackId | ||
? getSrcFromPlaybackId(props.playbackId, { domain: props.customDomain, token: props.tokens.playback }) | ||
: false}" | ||
src="${ | ||
!!props.src | ||
? props.src | ||
: props.playbackId | ||
? getSrcFromPlaybackId(props.playbackId, { domain: props.customDomain, token: props.tokens.playback }) | ||
: false | ||
}" | ||
poster="${ | ||
!!props.poster | ||
? props.poster | ||
: props.playbackId && !props.audio | ||
? getPosterURLFromPlaybackId(props.playbackId, { | ||
domain: props.customDomain, | ||
thumbnailTime: props.thumbnailTime ?? props.startTime, | ||
token: props.tokens.thumbnail, | ||
}) | ||
: false | ||
}" | ||
cast-src="${ | ||
!!props.src | ||
? props.src | ||
: props.playbackId | ||
? getSrcFromPlaybackId(props.playbackId, { domain: props.customDomain, token: props.tokens.playback }) | ||
: false | ||
}" | ||
cast-stream-type="${[StreamTypes.LIVE, StreamTypes.LL_LIVE].includes(props.streamType as any) ? 'live' : false}" | ||
> | ||
${props.playbackId && | ||
!props.audio && | ||
![StreamTypes.LIVE, StreamTypes.LL_LIVE, StreamTypes.DVR, StreamTypes.LL_DVR].includes(props.streamType as any) | ||
? html`<track | ||
label="thumbnails" | ||
default | ||
kind="metadata" | ||
src="${getStoryboardURLFromPlaybackId(props.playbackId, { | ||
domain: props.customDomain, | ||
token: props.tokens.storyboard, | ||
})}" | ||
/>` | ||
: html``} | ||
${ | ||
props.playbackId && | ||
!props.audio && | ||
![StreamTypes.LIVE, StreamTypes.LL_LIVE, StreamTypes.DVR, StreamTypes.LL_DVR].includes(props.streamType as any) | ||
? html`<track | ||
label="thumbnails" | ||
default | ||
kind="metadata" | ||
src="${getStoryboardURLFromPlaybackId(props.playbackId, { | ||
domain: props.customDomain, | ||
token: props.tokens.storyboard, | ||
})}" | ||
/>` | ||
: html`` | ||
} | ||
</mux-video> | ||
<button | ||
slot="seek-to-live-button" | ||
part="seek-live button" | ||
class="mxp-seek-to-live-button" | ||
aria-disabled="${props.inLiveWindow}" | ||
@@ -104,3 +123,2 @@ onclick="${function (this: HTMLButtonElement, evt: Event) { | ||
}}" | ||
class="mxp-seek-to-live-button" | ||
> | ||
@@ -118,14 +136,16 @@ Live | ||
${props.dialog?.message} | ||
${props.dialog?.linkUrl | ||
? html`<a | ||
href="${props.dialog.linkUrl}" | ||
target="_blank" | ||
rel="external noopener" | ||
aria-label="${props.dialog.linkText ?? ''} ${i18n(`(opens in a new window)`)}" | ||
>${props.dialog.linkText ?? props.dialog.linkUrl}</a | ||
>` | ||
: html``} | ||
${ | ||
props.dialog?.linkUrl | ||
? html`<a | ||
href="${props.dialog.linkUrl}" | ||
target="_blank" | ||
rel="external noopener" | ||
aria-label="${props.dialog.linkText ?? ''} ${i18n(`(opens in a new window)`)}" | ||
>${props.dialog.linkText ?? props.dialog.linkUrl}</a | ||
>` | ||
: html`` | ||
} | ||
</p> | ||
</mxp-dialog> | ||
</media-theme-mux> | ||
</${unsafeStatic(castThemeName(props.theme) ?? 'media-theme-mux')}> | ||
`; |
import type MuxVideoElement, { MediaError } from '@mux/mux-video'; | ||
export type MuxPlayerProps = Partial<MuxVideoElement> & { | ||
nohotkeys?: boolean; | ||
preferMse?: boolean; | ||
@@ -9,7 +10,5 @@ }; | ||
audio: boolean; | ||
theme?: string; | ||
playerSize: string; | ||
showLoading: boolean; | ||
hasCaptions: boolean; | ||
supportsAirPlay: boolean; | ||
supportsVolume: boolean; | ||
thumbnailTime: number; | ||
@@ -35,2 +34,3 @@ primaryColor: string; | ||
metadataViewerUserId: string; | ||
noHotKeys: boolean; | ||
}; | ||
@@ -37,0 +37,0 @@ |
import { VideoEvents } from '@mux/mux-video'; | ||
import type MuxVideoElement from '@mux/mux-video'; | ||
import * as logger from './logger'; | ||
import { toNumberOrUndefined } from './utils'; | ||
@@ -102,2 +103,3 @@ export type CastOptions = { | ||
this.media.muted = newValue != null; | ||
this.media.defaultMuted = newValue != null; | ||
} | ||
@@ -117,2 +119,3 @@ return; | ||
this.media.playbackRate = val; | ||
this.media.defaultPlaybackRate = val; | ||
} | ||
@@ -222,2 +225,15 @@ return; | ||
get defaultPlaybackRate() { | ||
return toNumberOrUndefined(this.getAttribute(CustomVideoAttributes.PLAYBACKRATE)) ?? 1; | ||
} | ||
set defaultPlaybackRate(val) { | ||
if (val != null) { | ||
this.setAttribute(CustomVideoAttributes.PLAYBACKRATE, `${val}`); | ||
} else { | ||
// Remove boolean attribute if false, 0, '', null, undefined. | ||
this.removeAttribute(CustomVideoAttributes.PLAYBACKRATE); | ||
} | ||
} | ||
get crossOrigin() { | ||
@@ -258,6 +274,16 @@ return getVideoAttribute(this, AllowedVideoAttributes.CROSSORIGIN); | ||
get muted() { | ||
return this.media?.muted ?? false; | ||
} | ||
set muted(val) { | ||
if (this.media) { | ||
this.media.muted = Boolean(val); | ||
} | ||
} | ||
get defaultMuted() { | ||
return getVideoAttribute(this, AllowedVideoAttributes.MUTED) != null; | ||
} | ||
set muted(val) { | ||
set defaultMuted(val) { | ||
if (val) { | ||
@@ -264,0 +290,0 @@ this.setAttribute(AllowedVideoAttributes.MUTED, ''); |
@@ -286,2 +286,6 @@ import { fixture, assert, aTimeout, waitUntil } from '@open-wc/testing'; | ||
assert(player.defaultMuted, 'player.defaultMuted is true'); | ||
assert(muxVideo.defaultMuted, 'muxVideo.defaultMuted is true'); | ||
assert(nativeVideo.defaultMuted, 'nativeVideo.defaultMuted is true'); | ||
player.removeAttribute('muted'); | ||
@@ -293,2 +297,6 @@ | ||
assert(!player.defaultMuted, 'player.defaultMuted is false'); | ||
assert(!muxVideo.defaultMuted, 'muxVideo.defaultMuted is false'); | ||
assert(!nativeVideo.defaultMuted, 'nativeVideo.defaultMuted is false'); | ||
player.setAttribute('muted', ''); | ||
@@ -299,2 +307,6 @@ | ||
assert(nativeVideo.muted, 'nativeVideo.muted is true'); | ||
assert(player.defaultMuted, 'player.defaultMuted is true'); | ||
assert(muxVideo.defaultMuted, 'muxVideo.defaultMuted is true'); | ||
assert(nativeVideo.defaultMuted, 'nativeVideo.defaultMuted is true'); | ||
}); | ||
@@ -348,2 +360,24 @@ | ||
it('defaultPlaybackRate property behaves like expected', async function () { | ||
const player = await fixture(`<mux-player | ||
playback-id="DS00Spx1CV902MCtPj5WknGlR102V5HFkDe" | ||
stream-type="on-demand" | ||
></mux-player>`); | ||
assert.equal(player.defaultPlaybackRate, 1); | ||
const muxVideo = player.media; | ||
const nativeVideo = muxVideo.shadowRoot.querySelector('video'); | ||
assert.equal(player.defaultPlaybackRate, 1, 'player.defaultPlaybackRate is 1'); | ||
assert.equal(muxVideo.defaultPlaybackRate, 1, 'muxVideo.defaultPlaybackRate is 1'); | ||
assert.equal(nativeVideo.defaultPlaybackRate, 1, 'nativeVideo.defaultPlaybackRate is 1'); | ||
player.defaultPlaybackRate = 0.7; | ||
assert.equal(player.defaultPlaybackRate, 0.7, 'player.defaultPlaybackRate is 0.7'); | ||
assert.equal(muxVideo.defaultPlaybackRate, 0.7, 'muxVideo.defaultPlaybackRate is 0.7'); | ||
assert.equal(nativeVideo.defaultPlaybackRate, 0.7, 'nativeVideo.defaultPlaybackRate is 0.7'); | ||
}); | ||
it("signing tokens generate correct asset URL's", async function () { | ||
@@ -350,0 +384,0 @@ // tokens expire in 10 years |
@@ -10,2 +10,4 @@ import { assert } from '@open-wc/testing'; | ||
const exportParts = `seek-live, layer, media-layer, poster-layer, vertical-layer, centered-layer, center, play, button, seek-backward, seek-forward, mute, captions, airplay, pip, fullscreen, cast, playbackrate, volume, range, time, display`; | ||
it('default template without props', function () { | ||
@@ -15,3 +17,3 @@ render(content({}), div); | ||
minify(div.innerHTML), | ||
'<media-theme-mux class="size-" stream-type="" player-size="" has-captions="" default-hidden-captions="" forward-seek-offset="" backward-seek-offset=""><mux-video slot="media" crossorigin="" playsinline="" player-software-name="" player-software-version=""></mux-video><button slot="seek-to-live-button" aria-disabled="" class="mxp-seek-to-live-button">\n Live\n </button><mxp-dialog no-auto-hide="" open=""><p></p></mxp-dialog></media-theme-mux>' | ||
`<media-theme-mux class="size-" stream-type="" player-size="" default-hidden-captions="" forward-seek-offset="" backward-seek-offset="" exportparts="${exportParts}"><mux-video slot="media" crossorigin="" playsinline="" player-software-name="" player-software-version=""></mux-video><button slot="seek-to-live-button" part="seek-live button" class="mxp-seek-to-live-button" aria-disabled="">\n Live\n </button><mxp-dialog no-auto-hide="" open=""><p></p></mxp-dialog></media-theme-mux>` | ||
); | ||
@@ -34,3 +36,3 @@ }); | ||
minify(div.innerHTML), | ||
'<media-theme-mux class="size-extra-small" stream-type="on-demand" player-size="extra-small" has-captions="" default-hidden-captions="" forward-seek-offset="" backward-seek-offset=""><mux-video slot="media" crossorigin="" playsinline="" player-software-name="" player-software-version="" stream-type="on-demand"></mux-video><button slot="seek-to-live-button" aria-disabled="" class="mxp-seek-to-live-button">\n Live\n </button><mxp-dialog no-auto-hide="" open=""><h3>Errr</h3><p></p></mxp-dialog></media-theme-mux>' | ||
`<media-theme-mux class="size-extra-small" stream-type="on-demand" player-size="extra-small" default-hidden-captions="" forward-seek-offset="" backward-seek-offset="" exportparts="${exportParts}"><mux-video slot="media" crossorigin="" playsinline="" player-software-name="" player-software-version="" stream-type="on-demand"></mux-video><button slot="seek-to-live-button" part="seek-live button" class="mxp-seek-to-live-button" aria-disabled="">\n Live\n </button><mxp-dialog no-auto-hide="" open=""><h3>Errr</h3><p></p></mxp-dialog></media-theme-mux>` | ||
); | ||
@@ -51,3 +53,3 @@ }); | ||
minify(div.innerHTML), | ||
'<media-theme-mux class="size-large" stream-type="on-demand" player-size="large" has-captions="" default-hidden-captions="" forward-seek-offset="" backward-seek-offset=""><mux-video slot="media" crossorigin="" playsinline="" player-software-name="" player-software-version="" stream-type="on-demand"></mux-video><button slot="seek-to-live-button" aria-disabled="" class="mxp-seek-to-live-button">\n Live\n </button><mxp-dialog no-auto-hide="" open=""><p></p></mxp-dialog></media-theme-mux>' | ||
`<media-theme-mux class="size-large" stream-type="on-demand" player-size="large" default-hidden-captions="" forward-seek-offset="" backward-seek-offset="" exportparts="${exportParts}"><mux-video slot="media" crossorigin="" playsinline="" player-software-name="" player-software-version="" stream-type="on-demand"></mux-video><button slot="seek-to-live-button" part="seek-live button" class="mxp-seek-to-live-button" aria-disabled="">\n Live\n </button><mxp-dialog no-auto-hide="" open=""><p></p></mxp-dialog></media-theme-mux>` | ||
); | ||
@@ -71,5 +73,5 @@ }); | ||
minify(div.innerHTML), | ||
'<media-theme-mux class="size-large" stream-type="on-demand" player-size="large" default-hidden-captions="" forward-seek-offset="" backward-seek-offset=""><mux-video slot="media" crossorigin="" playsinline="" player-software-name="" player-software-version="" stream-type="on-demand"></mux-video><button slot="seek-to-live-button" aria-disabled="" class="mxp-seek-to-live-button">\n Live\n </button><mxp-dialog no-auto-hide="" open=""><h3>Errr</h3><p></p></mxp-dialog></media-theme-mux>' | ||
`<media-theme-mux class="size-large" stream-type="on-demand" player-size="large" default-hidden-captions="" forward-seek-offset="" backward-seek-offset="" exportparts="${exportParts}"><mux-video slot="media" crossorigin="" playsinline="" player-software-name="" player-software-version="" stream-type="on-demand"></mux-video><button slot="seek-to-live-button" part="seek-live button" class="mxp-seek-to-live-button" aria-disabled="">\n Live\n </button><mxp-dialog no-auto-hide="" open=""><h3>Errr</h3><p></p></mxp-dialog></media-theme-mux>` | ||
); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
2
234
1
227264
45
3785
+ Added@mux/mux-video@0.8.5-canary.1-2a741f0(transitive)
+ Added@mux/playback-core@0.9.1-canary.10-2a741f0(transitive)
+ Addedmedia-chrome@0.9.0(transitive)
- Removed@mux/mux-video@0.8.1(transitive)
- Removed@mux/playback-core@0.8.0(transitive)
- Removedmedia-chrome@0.6.9(transitive)
Updatedmedia-chrome@0.9.0