@munsonlabs/video-player 0.2.5 → 0.2.7
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 +6 -694
- package/dist/chunks/ads-B8PU62NC.mjs +1 -0
- package/dist/chunks/brightcove-CPKzsnF0.mjs +1 -0
- package/dist/chunks/constants-Buc1xZvW.mjs +1 -0
- package/dist/chunks/dailymotion-DHCBjqaZ.mjs +1 -0
- package/dist/chunks/embedShared-R2ohosIo.mjs +1 -0
- package/dist/chunks/jwplayer-D8oOCmi4.mjs +1 -0
- package/dist/chunks/sourceHelpers-BNIZ7kmT.mjs +1 -0
- package/dist/chunks/vimeo-DC3q3YWJ.mjs +1 -0
- package/dist/chunks/youtube-e1csmnHD.mjs +1 -0
- package/dist/elements/chunks/ads-B8PU62NC.mjs +1 -0
- package/dist/elements/chunks/brightcove-CPKzsnF0.mjs +1 -0
- package/dist/elements/chunks/constants-Buc1xZvW.mjs +1 -0
- package/dist/elements/chunks/dailymotion-DHCBjqaZ.mjs +1 -0
- package/dist/elements/chunks/embedShared-R2ohosIo.mjs +1 -0
- package/dist/elements/chunks/font-KFHs3p_4-KFHs3p_4.mjs +1 -0
- package/dist/elements/chunks/jwplayer-D8oOCmi4.mjs +1 -0
- package/dist/elements/chunks/sourceHelpers-BNIZ7kmT.mjs +1 -0
- package/dist/elements/chunks/svg-BLBI83Ed-BjsPx0LR.mjs +1 -0
- package/dist/elements/chunks/vimeo-DC3q3YWJ.mjs +1 -0
- package/dist/elements/chunks/youtube-e1csmnHD.mjs +1 -0
- package/dist/elements/controls.css +1 -1
- package/dist/elements/controls.d.mts +23 -17
- package/dist/elements/controls.mjs +1 -1
- package/dist/elements/core.css +1 -1
- package/dist/elements/core.d.mts +32 -23
- package/dist/elements/core.mjs +1 -1
- package/dist/elements/index.d.mts +53 -40
- package/dist/elements/index.mjs +1 -1
- package/dist/elements/style.css +1 -1
- package/dist/index.d.mts +138 -88
- package/dist/index.mjs +1 -1
- package/dist/style.css +1 -1
- package/package.json +7 -6
- package/dist/chunks/ads-Dph3guS9.mjs +0 -1
- package/dist/chunks/brightcove-DiJF_74z.mjs +0 -1
- package/dist/chunks/constants-35ZnLsDv.mjs +0 -1
- package/dist/chunks/dailymotion-DTooszoV.mjs +0 -1
- package/dist/chunks/embedShared-CbArTtuR.mjs +0 -1
- package/dist/chunks/jwplayer-Dhq65VxK.mjs +0 -1
- package/dist/chunks/sourceHelpers-B0tH-grj.mjs +0 -1
- package/dist/chunks/vimeo-D0GAjId0.mjs +0 -1
- package/dist/chunks/youtube-B8AT2F-4.mjs +0 -1
- package/dist/elements/chunks/ads-Dph3guS9.mjs +0 -1
- package/dist/elements/chunks/brightcove-DiJF_74z.mjs +0 -1
- package/dist/elements/chunks/constants-35ZnLsDv.mjs +0 -1
- package/dist/elements/chunks/dailymotion-DTooszoV.mjs +0 -1
- package/dist/elements/chunks/embedShared-CbArTtuR.mjs +0 -1
- package/dist/elements/chunks/jwplayer-Dhq65VxK.mjs +0 -1
- package/dist/elements/chunks/sourceHelpers-B0tH-grj.mjs +0 -1
- package/dist/elements/chunks/vimeo-D0GAjId0.mjs +0 -1
- package/dist/elements/chunks/youtube-B8AT2F-4.mjs +0 -1
package/README.md
CHANGED
|
@@ -8,9 +8,7 @@ A Vue 3 video player supporting YouTube, Vimeo, Dailymotion, Brightcove, JW Play
|
|
|
8
8
|
npm install @munsonlabs/video-player
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
##
|
|
12
|
-
|
|
13
|
-
### Basic player
|
|
11
|
+
## Usage
|
|
14
12
|
|
|
15
13
|
```vue
|
|
16
14
|
<script setup>
|
|
@@ -21,700 +19,14 @@ import '@munsonlabs/video-player/style'
|
|
|
21
19
|
<template>
|
|
22
20
|
<VideoCard
|
|
23
21
|
label="Big Buck Bunny"
|
|
24
|
-
src="https://
|
|
25
|
-
poster="https://
|
|
22
|
+
src="https://archive.org/download/BigBuckBunny_124/Content/big_buck_bunny_720p_surround.mp4"
|
|
23
|
+
poster="https://img.youtube.com/vi/aqz-KE-bpKQ/0.jpg"
|
|
26
24
|
/>
|
|
27
25
|
</template>
|
|
28
26
|
```
|
|
29
27
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
Set `pin` on a standalone `VideoCard`/`VideoPlayer` (no `VideoStage` involved) to get the same corner-pinning mini-player behaviour `VideoStage` has, scoped to just that one player: once it scrolls out of view while playing, it pins to the given corner instead of auto-pausing, and shrinks back to its inline size once scrolled back into view.
|
|
33
|
-
|
|
34
|
-
```vue
|
|
35
|
-
<VideoCard src="https://cdn.jwplayer.com/videos/O5chtspP-4VHSaSK0.mp4" pin="bottom-right" />
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
- Only `'bottom-right'`, `'bottom-left'`, `'top-right'`, `'top-left'` are supported here (unlike `VideoStage`'s `pin`, there's no `'full-width'` option for a single player).
|
|
39
|
-
- Pausing while pinned does **not** unpin it - like a mini-player, it stays put, paused, in the corner until scrolled back into view. A dismiss button and a scroll-to-player button are shown on the pinned box.
|
|
40
|
-
- Omit `pin` to disable pinning entirely - the player then just auto-pauses offscreen as usual.
|
|
41
|
-
- `HideMarker` (see [Hiding the pinned stage over content](#hiding-the-pinned-stage-over-content)) works the same way here too, since there's only ever one pinned/tucked element on a page at a time.
|
|
42
|
-
|
|
43
|
-
### Stage player
|
|
44
|
-
|
|
45
|
-
`VideoStage` is a sticky full-width player that receives videos from `VideoCard` components anywhere on the page via window events. When the stage scrolls out of view it minifies to a pip in the bottom-right corner.
|
|
46
|
-
|
|
47
|
-
```vue
|
|
48
|
-
<script setup>
|
|
49
|
-
import { VideoStage, VideoCard } from '@munsonlabs/video-player'
|
|
50
|
-
import '@munsonlabs/video-player/style'
|
|
51
|
-
</script>
|
|
52
|
-
|
|
53
|
-
<template>
|
|
54
|
-
<VideoStage @state-change="onStateChange" />
|
|
55
|
-
<VideoCard v-for="video in videos" :key="video.src" v-bind="video" />
|
|
56
|
-
</template>
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
### Playlists
|
|
60
|
-
|
|
61
|
-
Pass `playlist` (a `VideoEntry[]`, the same shape as a `VideoCard`'s props) to let the stage track the currently-playing video's position within it. Position updates automatically when a user clicks a `VideoCard` elsewhere on the page, with nothing extra to keep in sync.
|
|
62
|
-
|
|
63
|
-
```vue
|
|
64
|
-
<VideoStage :playlist="videos" @state-change="onStateChange" />
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
- Auto-advance (playing the next entry on `ended`, no wraparound) is toggled via the controls popup's "Auto" button and remembered in `localStorage` - there's no prop for it.
|
|
68
|
-
- A template ref on `VideoStage` exposes `playNext()`, `playPrevious()`, `hasNext`, and `hasPrevious`:
|
|
69
|
-
|
|
70
|
-
```vue
|
|
71
|
-
<VideoStage ref="stage" :playlist="videos" />
|
|
72
|
-
<button :disabled="!stage?.hasPrevious" @click="stage.playPrevious()">Previous</button>
|
|
73
|
-
<button :disabled="!stage?.hasNext" @click="stage.playNext()">Next</button>
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
### Hiding the pinned stage over content
|
|
77
|
-
|
|
78
|
-
When `VideoStage` is pinned to a corner (any `pin` other than `'full-width'`), it can sit over content you'd rather it not cover - a footer, a signup form, a comments section. `HideMarker` is a plain sentinel element: place it just before that content, and the stage slides mostly off-screen (leaving a small sliver) for as long as the marker is in the viewport, sliding back once it isn't.
|
|
79
|
-
|
|
80
|
-
```vue
|
|
81
|
-
<script setup>
|
|
82
|
-
import { VideoStage, HideMarker } from '@munsonlabs/video-player'
|
|
83
|
-
</script>
|
|
84
|
-
|
|
85
|
-
<template>
|
|
86
|
-
<VideoStage pin="bottom-right" :playlist="videos" />
|
|
87
|
-
|
|
88
|
-
<!-- ...page content... -->
|
|
89
|
-
|
|
90
|
-
<HideMarker />
|
|
91
|
-
<Footer />
|
|
92
|
-
</template>
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
- There's no prop linking `HideMarker` to a specific `VideoStage` - there's only ever one stage on a page, so it always applies to whichever one is mounted.
|
|
96
|
-
- The sliver width is a CSS custom property: `--mlv-stage-tuck` (default `32px`), see [theming variables](#theming-the-hud-buttons).
|
|
97
|
-
- Place it precisely where you want the stage to tuck away - it renders a 1px-tall invisible `div`, so it triggers briefly as the page scrolls past that exact line.
|
|
98
|
-
|
|
99
|
-
---
|
|
100
|
-
|
|
101
|
-
## VideoCard / VideoPlayer Props
|
|
102
|
-
|
|
103
|
-
| Prop | Type | Default | Description |
|
|
104
|
-
| ------------------- | -------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
105
|
-
| `src` | `string` | - | **Required.** The video URL or platform-specific URI (the platform is auto-detected from its shape, see [Platform URLs](#platform-urls)) |
|
|
106
|
-
| `label` | `string` | `''` | Video label, shown on the placeholder and used as the player's accessible name |
|
|
107
|
-
| `poster` | `string` | `''` | Poster image URL |
|
|
108
|
-
| `aspectRatio` | `string` | `'16:9'` | e.g. `'16:9'`, `'9:16'`, `'4:3'` |
|
|
109
|
-
| `autoplay` | `boolean` | `false` | Autoplay on mount, see [Autoplay, mute & volume](#autoplay-mute--volume) |
|
|
110
|
-
| `muted` | `boolean` | - | Force a starting mute state - if unset, follows the shared audio preference, see [Autoplay, mute & volume](#autoplay-mute--volume) |
|
|
111
|
-
| `volume` | `number` | - | Force a starting volume (0-1) - if unset, follows the shared audio preference, see [Autoplay, mute & volume](#autoplay-mute--volume) |
|
|
112
|
-
| `lazy` | `boolean` | `true` | `VideoCard` only: show the placeholder until clicked |
|
|
113
|
-
| `nativeUi` | `boolean` | `false` | Use the platform's native controls (YouTube, Vimeo, Dailymotion only) |
|
|
114
|
-
| `autoStage` | `boolean` | `false` | `VideoCard` only: send this video to the stage on mount |
|
|
115
|
-
| `playbackRate` | `number` | `1` | Initial playback rate |
|
|
116
|
-
| `loop` | `boolean` | `false` | Start with looping on - reactive, so flipping it later is the same as calling `toggleLoop()` |
|
|
117
|
-
| `preload` | `PreloadMode` | - | `'none' \| 'metadata' \| 'auto'` - the native `<video preload>` attribute. `'none'` defers loading for MP4 and HLS alike (hls.js is held back until the first play), not DASH; other values leave HLS as normal |
|
|
118
|
-
| `adTagUrl` | `string` | `''` | VAST or VMAP ad tag URL |
|
|
119
|
-
| `adMacroParams` | `object` | - | Fills `{macro}` tokens on whichever ad tag URL ends up in use, see [Ads](#ads-ima--vast--vmap) |
|
|
120
|
-
| `headerBidding` | `object` | - | Runs a Prebid.js auction before the ad plays, see [Header bidding](#header-bidding-prebidjs) |
|
|
121
|
-
| `tracks` | `array` | - | WebVTT caption/subtitle tracks, see [Captions](#captions-webvtt) |
|
|
122
|
-
| `payload` | `object` | `{}` | Arbitrary data attached to every `state-change` event |
|
|
123
|
-
| `action` | `PlayerAction` | `null` | Button shown in the player HUD, see [Actions](#actions) |
|
|
124
|
-
| `disableTapCapture` | `boolean` | `false` | Disables the full-video tap-to-reveal-controls overlay (tap/click still reaches embed platform UI underneath, e.g. YouTube's own) |
|
|
125
|
-
| `controls` | `boolean` | `true` | Set `false` to render a bare `<video>` with no built-in HUD - drive playback with your own UI instead, see [Headless Controls](#headless-controls) |
|
|
126
|
-
| `playInView` | `boolean` | `false` | Auto-play once at least half the player is visible, auto-pause once it isn't - e.g. for a scroll-snap feed, see [Autoplay, mute & volume](#autoplay-mute--volume) |
|
|
127
|
-
| `pin` | `PinCorner` | - | Pins the player to this screen corner once it scrolls out of view while playing, instead of auto-pausing - see [Pinning a single player](#pinning-a-single-player) |
|
|
128
|
-
|
|
129
|
-
### Autoplay, mute & volume
|
|
130
|
-
|
|
131
|
-
Unless a video sets `muted`/`volume` explicitly, every player on the page shares one persisted audio preference (`localStorage`) - a newly-mounted player starts at whatever level the viewer last chose anywhere else. **Autoplay without a user gesture always starts muted** (browser policy, not a library choice). A real user gesture (clicking a lazy placeholder, `video-toggle`) follows the shared preference immediately. `VideoStage`'s playlist skip/auto-advance carries the outgoing video's own mute/volume state forward, not the shared preference. An explicit `muted`/`volume` always wins.
|
|
132
|
-
|
|
133
|
-
### Platform URLs
|
|
134
|
-
|
|
135
|
-
The platform is detected automatically from the shape of `src`; there's no separate prop to set it.
|
|
136
|
-
|
|
137
|
-
| Platform | URL format |
|
|
138
|
-
| ------------- | -------------------------------------------------------------------------------------------------------- |
|
|
139
|
-
| `youtube` | `https://www.youtube.com/watch?v=VIDEO_ID` |
|
|
140
|
-
| `vimeo` | `https://vimeo.com/VIDEO_ID` |
|
|
141
|
-
| `dailymotion` | `https://www.dailymotion.com/video/VIDEO_ID` |
|
|
142
|
-
| `brightcove` | `https://players.brightcove.net/ACCOUNT_ID/PLAYER_ID_EMBED/index.html?videoId=VIDEO_ID`\* |
|
|
143
|
-
| `jwplayer` | `https://cdn.jwplayer.com/videos/...` · `https://cdn.jwplayer.com/manifests/...` · `jwplayer://MEDIA_ID` |
|
|
144
|
-
| `html5` | Any direct MP4 / HLS / DASH URL |
|
|
145
|
-
|
|
146
|
-
\* `PLAYER_ID_EMBED` is one path segment: `{PLAYER_ID}_{EMBED_NAME}` (e.g. `abc123_default` for the account's default player), which is exactly the URL Brightcove Studio's "Publish" panel gives you for an embed.
|
|
147
|
-
|
|
148
|
-
### Adding a custom platform
|
|
149
|
-
|
|
150
|
-
`registerPlatform` teaches the player about a platform beyond the six above, without forking the package:
|
|
151
|
-
|
|
152
|
-
```ts
|
|
153
|
-
import { registerPlatform } from '@munsonlabs/video-player'
|
|
154
|
-
|
|
155
|
-
registerPlatform({
|
|
156
|
-
key: 'acme',
|
|
157
|
-
test: (url) => url.includes('acme.tv'),
|
|
158
|
-
embed: true, // true: the platform has its own SDK/iframe; false: it resolves to a plain playable file
|
|
159
|
-
createAdapter: (videoEl, options) => createAcmeAdapter(videoEl, options),
|
|
160
|
-
})
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
Call it once, before mounting any player that might see the new URL - resolution reads the registry fresh every time, so ordering (not caching) is the only thing that matters.
|
|
164
|
-
|
|
165
|
-
- **`embed: true`** - the platform owns its own player (YouTube, Twitch, Vimeo). `createAdapter` returns a full `PlaybackAdapter`: `play` (returns a promise, resolved once the request is issued)/`pause`/`currentTime`/captions/quality/PiP/fullscreen/`on`/`off`/`dispose`, translating SDK events into the common set.
|
|
166
|
-
- **`embed: false`** - the platform hosts a file/manifest behind an opaque URL. `resolveSource` (optional) returns `{ src, type?, poster?, adTagUrl? }`; the native path handles playback.
|
|
167
|
-
|
|
168
|
-
This works identically from the [web component bundles](#web-component-usage) - see [Registering a platform from a web component](#registering-a-platform-from-a-web-component) for the `?defer` import mode that sidesteps a timing subtlety with statically-declared tags.
|
|
169
|
-
|
|
170
|
-
---
|
|
171
|
-
|
|
172
|
-
## Headless Controls
|
|
173
|
-
|
|
174
|
-
Unstyled control primitives for building your own HUD instead of the built-in one (set `:controls="false"` on the player) - each is published both as a Vue component and as a `ml-controls-*` custom element from the exact same source:
|
|
175
|
-
|
|
176
|
-
Building a custom HUD out of these and never rendering `VideoPlayer`'s own built-in controls? Import `@munsonlabs/video-player/style/controls` instead of the full `/style` bundle to skip the built-in HUD/popup CSS - see [Web Component Usage](#web-component-usage) for the full breakdown of the three style bundles.
|
|
177
|
-
|
|
178
|
-
```ts
|
|
179
|
-
import {
|
|
180
|
-
PlayButton,
|
|
181
|
-
MuteButton,
|
|
182
|
-
FullscreenButton,
|
|
183
|
-
LoopButton,
|
|
184
|
-
PipButton,
|
|
185
|
-
CaptionsButton,
|
|
186
|
-
QualityButton,
|
|
187
|
-
PlaybackRateButton,
|
|
188
|
-
Buffering,
|
|
189
|
-
Scrubber,
|
|
190
|
-
VolumeSlider,
|
|
191
|
-
TimeDisplay,
|
|
192
|
-
Transcript,
|
|
193
|
-
} from '@munsonlabs/video-player'
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
Every control takes a `player` prop pointing at the same object `VideoPlayer`/`VideoCard`/`VideoStage` expose via a template ref:
|
|
197
|
-
|
|
198
|
-
```vue
|
|
199
|
-
<script setup>
|
|
200
|
-
import { ref } from 'vue'
|
|
201
|
-
import { VideoPlayer, PlayButton, MuteButton, Scrubber } from '@munsonlabs/video-player'
|
|
202
|
-
const player = ref(null)
|
|
203
|
-
</script>
|
|
204
|
-
|
|
205
|
-
<template>
|
|
206
|
-
<VideoPlayer ref="player" src="..." :controls="false" />
|
|
207
|
-
<PlayButton :player="player" />
|
|
208
|
-
<MuteButton :player="player" />
|
|
209
|
-
<Scrubber :player="player" />
|
|
210
|
-
</template>
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
Or, `<label for>`-style, skip the ref and point at an element id instead:
|
|
214
|
-
|
|
215
|
-
```vue
|
|
216
|
-
<VideoPlayer id="my-player" src="..." :controls="false" />
|
|
217
|
-
<PlayButton for="my-player" />
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
`player` and `for` are both optional - if both are given, `player` wins. Each button's own CSS uses `:where()` (zero specificity), so a single class you add always wins, and most expose their state via a scoped slot for full custom markup (e.g. `PlayButton`'s `#default="{ isPlaying }"`). See [Web Component Usage](#web-component-usage) for using these as raw custom elements (`<ml-controls-play-button>` etc.) outside Vue.
|
|
221
|
-
|
|
222
|
-
### The player handle
|
|
223
|
-
|
|
224
|
-
A template ref on `VideoPlayer`/`VideoCard`/`VideoStage` (or the `player` injected into a headless control) exposes the `PlayerHandle` type. The commonly used part:
|
|
225
|
-
|
|
226
|
-
| Member | Notes |
|
|
227
|
-
| -------------------------------------------------- | -------------------------------------------------------------------------------------------- |
|
|
228
|
-
| `play()` / `pause()` / `togglePlay()` | `play()` returns a promise that resolves once playing and rejects on an autoplay block/error |
|
|
229
|
-
| `replay()` | Seeks to 0 and plays; what the HUD button does after `ended` |
|
|
230
|
-
| `seekTo(seconds)` / `seek(percent)` | Seconds for transcripts, chapters, deep links; percent for scrubbers |
|
|
231
|
-
| `isLoaded` / `isReady` / `isPlaying` | `isLoaded` means the duration is known; `isReady` only means the adapter is attached |
|
|
232
|
-
| `hasEnded` / `isError` / `retry()` | `retry()` reloads the current source after an error |
|
|
233
|
-
| `current` / `total` / `isLive` | Seconds |
|
|
234
|
-
| `isMuted` / `vol` / `toggleMute()` / `setVolume()` | Volume is 0-1 |
|
|
235
|
-
|
|
236
|
-
### Transcript
|
|
237
|
-
|
|
238
|
-
`Transcript` renders a clickable transcript for the linked player: pass `cues` as `{ time, end?, text }[]` (times in seconds), clicking a cue seeks to its timestamp (starting playback first if paused), and the cue at the playhead is highlighted and kept scrolled into view:
|
|
239
|
-
|
|
240
|
-
```vue
|
|
241
|
-
<VideoPlayer id="my-player" src="..." />
|
|
242
|
-
<Transcript
|
|
243
|
-
for="my-player"
|
|
244
|
-
:cues="[
|
|
245
|
-
{ time: 0, text: 'Welcome back to the show.' },
|
|
246
|
-
{ time: 12, text: 'Today we look at the new release.' },
|
|
247
|
-
{ time: 47, end: 60, text: 'Here is the demo.' },
|
|
248
|
-
]"
|
|
249
|
-
/>
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
- Works on **every** platform (YouTube/Vimeo/Dailymotion included) - only needs current time and `seek`.
|
|
253
|
-
- `end` is optional: when set, nothing is highlighted between that cue's `end` and the next cue's start.
|
|
254
|
-
- Auto-scroll pauses while the pointer is over the list. A scoped slot customises each cue's markup: `#default="{ cue, index, isActive, formatTime }"`.
|
|
255
|
-
- As a custom element (`<ml-controls-transcript>`), pass cues as an inline JSON string attribute: `cues='[{"time":0,"text":"..."}]'`.
|
|
256
|
-
|
|
257
|
-
### Building your own wrapper component
|
|
258
|
-
|
|
259
|
-
`useForwardedPlayer` is what `VideoCard`/`VideoStage` themselves use to forward a template-ref'd `VideoPlayer`'s controls/state onto their own `defineExpose` - the same mechanism is available for building a custom wrapper component of your own:
|
|
260
|
-
|
|
261
|
-
```vue
|
|
262
|
-
<script setup>
|
|
263
|
-
import { useForwardedPlayer } from '@munsonlabs/video-player'
|
|
264
|
-
const { playerRef, forwarded } = useForwardedPlayer()
|
|
265
|
-
|
|
266
|
-
defineExpose(forwarded)
|
|
267
|
-
</script>
|
|
268
|
-
|
|
269
|
-
<template>
|
|
270
|
-
<VideoPlayer ref="playerRef" v-bind="$attrs" />
|
|
271
|
-
</template>
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
`useForwardedPlayer` owns the ref itself - bind `playerRef` on the wrapped `VideoPlayer`, and spread (or pass) `forwarded` into your own `defineExpose`, so callers holding a ref to your wrapper get the same `PlayerHandle` API `VideoPlayer` exposes (methods return promises there, since the optional `guard` callback that runs before each call may be async).
|
|
275
|
-
|
|
276
|
-
Because it's wired up via `defineExpose`, this works the same way whether your wrapper is used as a Vue component (a template ref) or as a custom element ([Web Component Usage](#web-component-usage)) - Vue copies `defineExpose`d properties onto the element itself.
|
|
277
|
-
|
|
278
|
-
### Exposing a plain Vue player to independent, non-Vue code
|
|
279
|
-
|
|
280
|
-
A plain `<VideoPlayer>`/`<VideoCard>`/`<VideoStage>` used the normal Vue way doesn't expose its API onto the DOM - its state lives on the Vue component instance, reachable only via a template ref inside your own app. `exposePlayerOnElement` bridges that gap for code with no access to your Vue app (a third party's own script, a `<ml-controls-*>` custom element from a separate bundle):
|
|
281
|
-
|
|
282
|
-
```vue
|
|
283
|
-
<script setup>
|
|
284
|
-
import { ref, onMounted } from 'vue'
|
|
285
|
-
import { VideoPlayer, exposePlayerOnElement } from '@munsonlabs/video-player'
|
|
286
|
-
|
|
287
|
-
const playerRef = ref(null)
|
|
288
|
-
const wrapperEl = ref(null)
|
|
289
|
-
onMounted(() => exposePlayerOnElement(wrapperEl.value, playerRef.value))
|
|
290
|
-
</script>
|
|
291
|
-
|
|
292
|
-
<template>
|
|
293
|
-
<div id="my-player" ref="wrapperEl">
|
|
294
|
-
<VideoPlayer ref="playerRef" src="..." />
|
|
295
|
-
</div>
|
|
296
|
-
</template>
|
|
297
|
-
```
|
|
298
|
-
|
|
299
|
-
```html
|
|
300
|
-
<!-- a completely separate script/bundle, e.g. a third party's own -->
|
|
301
|
-
<script type="module">
|
|
302
|
-
import '@munsonlabs/video-player/element/controls'
|
|
303
|
-
</script>
|
|
304
|
-
<ml-controls-transcript for="my-player" cues='[{"time":0,"text":"..."}]'></ml-controls-transcript>
|
|
305
|
-
```
|
|
306
|
-
|
|
307
|
-
State fields are copied as live getters (not a one-time snapshot) and methods directly. `el` doesn't need to be the player's own root element - a wrapper `<div>` works the same.
|
|
308
|
-
|
|
309
|
-
---
|
|
310
|
-
|
|
311
|
-
## Events
|
|
312
|
-
|
|
313
|
-
### `state-change`
|
|
314
|
-
|
|
315
|
-
Emitted by `VideoCard`, `VideoPlayer`, and `VideoStage`. Every event includes `currentTime`, `duration`, `src`, and `payload` (arbitrary user data).
|
|
316
|
-
|
|
317
|
-
| Event | When it fires | Extra fields |
|
|
318
|
-
| -------------------------------- | ---------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
|
|
319
|
-
| `loaded` | Duration is known (or the stream is detected as live); `isLoaded` flips true | — |
|
|
320
|
-
| `play` | Playback starts or resumes | — |
|
|
321
|
-
| `pause` | Playback pauses | — |
|
|
322
|
-
| `ended` | Playback reaches the end | — |
|
|
323
|
-
| `seeked` | Seek completes | — |
|
|
324
|
-
| `error` | Source fails to load/play; player shows a "Retry" button | `error: { code, message }` |
|
|
325
|
-
| `adstart` | Ad begins playing | — |
|
|
326
|
-
| `adend` | Ad finishes | — |
|
|
327
|
-
| `volumechange` | Volume or mute state changes | `isMuted` |
|
|
328
|
-
| `ratechange` | Playback rate changes | `playbackRate` |
|
|
329
|
-
| `captionchange` | Active caption track changes (fires on `setCaptionTrack()` and ABR-driven changes, but not at mount) | `captionIndex` (null = off) |
|
|
330
|
-
| `qualitychange` | Active quality level changes (fires on `setQuality()` and ABR-driven changes, but not at mount) | `qualityIndex` (null = Auto) |
|
|
331
|
-
| `pipchange` | PiP entered or exited (from player button or browser floating window) | `isPipActive` |
|
|
332
|
-
| `loopchange` | Loop toggled (not before playback starts) | `isLooping` |
|
|
333
|
-
| `firstQuartile` | Playback crosses 25% of duration | — |
|
|
334
|
-
| `midpoint` | Playback crosses 50% of duration | — |
|
|
335
|
-
| `thirdQuartile` | Playback crosses 75% of duration | — |
|
|
336
|
-
| `controlsopen` / `controlsclose` | Expanded controls popup mounts/unmounts | `element` (popup root node; on close, a detached snapshot) |
|
|
337
|
-
| `stageopen` / `stageclose` | Stage mounts its inner player / tears it down (stage only) | — |
|
|
338
|
-
| `bufferstart` / `bufferend` | Buffering begins (after stall delay) / resolves | — |
|
|
339
|
-
| `timeupdate` | Regular time update during playback | — |
|
|
340
|
-
| `tap` | Full-video tap-to-reveal overlay is tapped; only signal for `controls: false` consumers | — |
|
|
341
|
-
|
|
342
|
-
`firstQuartile`/`midpoint`/`thirdQuartile` reset and fire again on loop restart. `captionchange`/`qualitychange` never fire before playback starts.
|
|
343
|
-
|
|
344
|
-
---
|
|
345
|
-
|
|
346
|
-
## Actions
|
|
347
|
-
|
|
348
|
-
The `action` prop adds a button to the player HUD. Pass a built-in string or a custom action object.
|
|
349
|
-
|
|
350
|
-
### Built-in actions
|
|
351
|
-
|
|
352
|
-
```vue
|
|
353
|
-
<VideoCard action="mute" ... />
|
|
354
|
-
<VideoCard action="loop" ... />
|
|
355
|
-
<VideoCard action="autoplay" ... />
|
|
356
|
-
```
|
|
357
|
-
|
|
358
|
-
`action="autoplay"` toggles [auto-advance](#playlists) and only makes sense when rendered inside a `VideoStage` with a `playlist` set: it reads and flips the same `autoAdvance` state as the controls popup's built-in "Auto" button, just as a HUD button instead.
|
|
359
|
-
|
|
360
|
-
### Custom action
|
|
361
|
-
|
|
362
|
-
Pass an object with `icon` (SVG string), `label`, and `onClick`:
|
|
363
|
-
|
|
364
|
-
```vue
|
|
365
|
-
<VideoCard :action="{ icon: '<svg .../>', label: 'Save', onClick: () => console.log('saved') }" ... />
|
|
366
|
-
```
|
|
367
|
-
|
|
368
|
-
---
|
|
369
|
-
|
|
370
|
-
## Ads (IMA / VAST / VMAP)
|
|
371
|
-
|
|
372
|
-
**Step by step:**
|
|
373
|
-
|
|
374
|
-
1. Get a VAST or VMAP ad tag URL. For testing, use [Google's public IMA sample tags](https://developers.google.com/interactive-media-ads/docs/sdks/html5/client-side/tags); for production, this comes from your ad server (Google Ad Manager): ask whoever manages ad ops for the tag, or build it yourself if you already know the ad unit path.
|
|
375
|
-
2. Pass it as `adTagUrl` on `VideoCard`/`VideoPlayer`. That's it for a basic pre-roll/VMAP schedule: the Google IMA SDK is loaded automatically on demand, nothing else to configure.
|
|
376
|
-
3. Confirm it's working: watch for `adstart`/`adend` on the `state-change` event, or check the Network tab for a request to `pubads.g.doubleclick.net` (or wherever your ad server lives) when the video plays.
|
|
377
|
-
4. Only if your ad tag is a **macro template** (contains literal `{tokenName}` placeholders, as Brightcove's auto-discovered ad tags usually do, see below) do you need `adMacroParams` too. Otherwise skip it entirely.
|
|
378
|
-
|
|
379
|
-
```vue
|
|
380
|
-
<VideoCard src="https://..." ad-tag-url="https://pubads.g.doubleclick.net/..." />
|
|
381
|
-
```
|
|
382
|
-
|
|
383
|
-
Ads are not supported on YouTube, Vimeo, or Dailymotion.
|
|
384
|
-
|
|
385
|
-
Some platforms (currently Brightcove) auto-discover their own ad tag URL: the platform's own wins if you don't pass `adTagUrl` yourself. Brightcove's ad tag URLs are typically macro templates (e.g. `...&iu={adUnit}&vid={referenceId}&cust_params={customParameters}`), not ready-to-use URLs. `adMacroParams` fills these in on **whichever** ad tag URL ends up in use, prop-supplied or auto-discovered:
|
|
386
|
-
|
|
387
|
-
```vue
|
|
388
|
-
<VideoCard src="https://players.brightcove.net/..." :ad-tag-params="{ adUnit: 'network/section', referenceId: videoId, rdid: deviceId }" />
|
|
389
|
-
```
|
|
390
|
-
|
|
391
|
-
Name each key exactly after the macro it fills (`vid={referenceId}` means the key is `referenceId`, not `vid`). A key with no matching `{macro}` is a no-op.
|
|
392
|
-
|
|
393
|
-
### Header bidding (Prebid.js)
|
|
394
|
-
|
|
395
|
-
**Step by step:**
|
|
396
|
-
|
|
397
|
-
1. Confirm your organisation already runs Prebid.js somewhere on the page (`window.pbjs` in the console). If not, this isn't something to set up from this package.
|
|
398
|
-
2. Get a Prebid **video ad unit** config from ad ops (with `code`, `mediaTypes.video`, and `bids`) and the GAM ad unit path (`iu`).
|
|
399
|
-
3. Pass both as `headerBidding: { adUnit, params: { iu: '...' } }`, alongside a plain `adTagUrl`/`adMacroParams` as fallback. Never rely on `headerBidding` alone.
|
|
400
|
-
4. Verify: run `pbjs.getBidResponsesForAdUnitCode('your-ad-unit-code')` in the console after playing.
|
|
401
|
-
|
|
402
|
-
The auction runs **in the background** and is never awaited: it can't block mounting, playback, or controls. Its result is only used if ready by the time the ad is requested (first play); otherwise the plain ad tag is used. Every failure mode (no `pbjs`, timeout, no bid, `buildVideoUrl()` throwing) fails open:
|
|
403
|
-
|
|
404
|
-
```vue
|
|
405
|
-
<VideoCard
|
|
406
|
-
src="https://players.brightcove.net/..."
|
|
407
|
-
:header-bidding="{
|
|
408
|
-
adUnit: {
|
|
409
|
-
code: 'video-preroll',
|
|
410
|
-
mediaTypes: { video: { context: 'instream', playerSize: [640, 480] } },
|
|
411
|
-
bids: [{ bidder: 'appnexus', params: { placementId: 123456 } }],
|
|
412
|
-
},
|
|
413
|
-
params: { iu: '/network/adunit' },
|
|
414
|
-
timeoutMs: 1000,
|
|
415
|
-
}"
|
|
416
|
-
/>
|
|
417
|
-
```
|
|
418
|
-
|
|
419
|
-
`adUnit` is a standard Prebid video ad unit. `params` is passed through to `buildVideoUrl()` (GAM ad tag params). `timeoutMs` (default `1000`) bounds the auction. Every failure mode fails open to whatever `adTagUrl`/`adMacroParams` would have resolved to.
|
|
420
|
-
|
|
421
|
-
### Ad overlay UI
|
|
422
|
-
|
|
423
|
-
While an ad is playing, a small overlay shows an "Ad" badge with a countdown to the ad's end, a dedicated pause/resume button, and a mute button that controls the ad creative's own independent audio (separate from the content video's volume/mute state: muting the player has no effect on ad audio, and vice versa).
|
|
424
|
-
|
|
425
|
-
### Ad visibility behaviour
|
|
426
|
-
|
|
427
|
-
An ad pauses itself whenever the tab is hidden or the window loses focus, and exits Picture-in-Picture if active (skipped on iOS, where the ad renders into the same `<video>` element PiP mirrors). The player ignores `timeupdate`/`durationchange`/`progress` events while an ad is active, so `current`/`total`/`bufferedDisplay` stay on the content's values throughout. On iOS, after a post-roll ad finishes the player restores the original content source and seeks to the end, since the IMA SDK may not restore it.
|
|
428
|
-
|
|
429
|
-
---
|
|
430
|
-
|
|
431
|
-
## Captions (WebVTT)
|
|
432
|
-
|
|
433
|
-
Only meaningful for the native `<video>` path (plain MP4/HLS): captions are unsupported on YouTube, Vimeo, and Dailymotion, which render their own captions inside their iframe instead if the source video has them. Brightcove and JW Player get real support for free, since both resolve to an actual `<video>`/HLS source under the hood.
|
|
434
|
-
|
|
435
|
-
There are two ways a caption track ends up available, and you don't have to pick: the player supports both at once.
|
|
436
|
-
|
|
437
|
-
- **HLS streams that already declare subtitle renditions in their own manifest** get picked up automatically by hls.js/Safari's native HLS, with no `tracks` prop needed at all. A "Captions" option just appears in the controls menu once the manifest has parsed.
|
|
438
|
-
- **Everything else** (a plain MP4, or an HLS stream with no subtitle rendition of its own) needs WebVTT tracks supplied explicitly via `tracks`:
|
|
439
|
-
|
|
440
|
-
```vue
|
|
441
|
-
<VideoCard
|
|
442
|
-
src="https://.../video.mp4"
|
|
443
|
-
:tracks="[
|
|
444
|
-
{ src: '/captions/en.vtt', kind: 'captions', srclang: 'en', label: 'English', default: true },
|
|
445
|
-
{ src: '/captions/fr.vtt', kind: 'captions', srclang: 'fr', label: 'Français' },
|
|
446
|
-
]"
|
|
447
|
-
/>
|
|
448
|
-
```
|
|
449
|
-
|
|
450
|
-
`src` must be a WebVTT file URL (`<track>` only understands VTT, not SRT). Each entry renders as a native `<track>` element: the browser fetches and parses the file itself. `default: true` has the browser show that track immediately with no extra wiring.
|
|
451
|
-
|
|
452
|
-
Clicking "Captions" in the controls menu cycles Off → first track → next track → ... → Off. Programmatically, a template ref on `VideoPlayer` exposes `setCaptionTrack(index)` (`null` turns captions off); see [Events](#state-change) for the `captionchange` event this fires.
|
|
453
|
-
|
|
454
|
-
---
|
|
455
|
-
|
|
456
|
-
## Video Quality (HLS / DASH)
|
|
457
|
-
|
|
458
|
-
Adaptive HLS and DASH streams automatically expose a "Quality" option in the controls menu once hls.js/dash.js (or Safari's native HLS) has parsed the manifest's resolution variants: there's no prop to set and nothing to configure. Clicking it cycles Auto → highest → ... → lowest → Auto; Auto leaves the streaming engine's adaptive bitrate algorithm in control.
|
|
459
|
-
|
|
460
|
-
This only applies to HLS and DASH: plain MP4/HTML5 sources have no variant levels, and it's unsupported on YouTube, Vimeo, and Dailymotion. Brightcove and JW Player get it for free, same reasoning as captions above.
|
|
461
|
-
|
|
462
|
-
**Safari plays HLS natively** (see [Peer Dependencies](#peer-dependencies)), and native HLS has no manual quality-override hook: the Quality option simply won't appear there.
|
|
463
|
-
|
|
464
|
-
Programmatically, a template ref on `VideoPlayer` exposes `setQuality(index)` (`null` re-enables Auto); see [Events](#state-change) for the `qualitychange` event this fires.
|
|
465
|
-
|
|
466
|
-
---
|
|
467
|
-
|
|
468
|
-
## Picture-in-Picture
|
|
469
|
-
|
|
470
|
-
A Picture-in-Picture button appears in the controls automatically wherever the browser supports it (`document.pictureInPictureEnabled`), nothing to configure. It stays in sync whether PiP is toggled from the player's own button or the browser's floating window is closed directly.
|
|
471
|
-
|
|
472
|
-
Only supported on the native `<video>` path (plain MP4/HLS): YouTube, Vimeo, and Dailymotion are unsupported here since their iframe content isn't a real `<video>` element this package controls directly. Brightcove and JW Player get it for free, same reasoning as captions/quality above.
|
|
473
|
-
|
|
474
|
-
Programmatically, a template ref on `VideoPlayer` exposes `togglePip()`; see [Events](#state-change) for the `pipchange` event this fires.
|
|
475
|
-
|
|
476
|
-
---
|
|
477
|
-
|
|
478
|
-
## Theming the HUD buttons
|
|
479
|
-
|
|
480
|
-
Set the variables on any ancestor of the player (e.g. a wrapper `<div>` or `:root`) to restyle all three buttons at once:
|
|
481
|
-
|
|
482
|
-
```css
|
|
483
|
-
.my-video-wrapper {
|
|
484
|
-
--mlv-btn-bg: #e11d48;
|
|
485
|
-
--mlv-btn-color: #fff;
|
|
486
|
-
}
|
|
487
|
-
```
|
|
488
|
-
|
|
489
|
-
Icons use `fill="currentColor"`, so `--mlv-btn-color` recolours the icon along with `--mlv-btn-bg` for the background. Hover state is a `filter: brightness()` lift on top of `--mlv-btn-bg`, so it works for both the default translucent look and a solid theme colour.
|
|
490
|
-
|
|
491
|
-
### Full controls popup
|
|
492
|
-
|
|
493
|
-
| Variable | Default | Description |
|
|
494
|
-
| ---------------------- | ------------------------------- | ---------------------------------------------------------------------------------------- |
|
|
495
|
-
| `--mlv-controls-width` | `min(450px, calc(100% - 32px))` | Controls popup width |
|
|
496
|
-
| `--mlv-popup-align` | `center` | Horizontal alignment of the popup (`center` or `flex-end` to dock at the bottom) |
|
|
497
|
-
| `--mlv-btn-bg` | (frosted glass) | Background of action, play/pause, and show-controls buttons |
|
|
498
|
-
| `--mlv-btn-color` | (frosted glass) | Icon/text colour of the same buttons |
|
|
499
|
-
| `--mlv-stage-tuck` | `32px` | Width of the sliver left visible when `HideMarker` tucks the pinned stage off-screen |
|
|
500
|
-
| `--mlv-radius` | `12px` | Border-radius of the pinned corner box |
|
|
501
|
-
| `--mlv-accent` | — | Active-state highlight colour for the loop/autoplay toggle buttons in the controls popup |
|
|
502
|
-
|
|
503
|
-
Real fullscreen always docks the popup to the bottom regardless of `--mlv-popup-align`.
|
|
504
|
-
|
|
505
|
-
### Fullscreen controls
|
|
506
|
-
|
|
507
|
-
Outside fullscreen, the full controls bar only appears after clicking "show controls" and stays open until dismissed. In real fullscreen it auto-shows on activity and auto-hides after inactivity, like a standard native player.
|
|
508
|
-
|
|
509
|
-
---
|
|
510
|
-
|
|
511
|
-
## Web Component Usage
|
|
512
|
-
|
|
513
|
-
Import an element bundle to register components as native custom elements. These render into the light DOM (not shadow roots), so styling has nowhere encapsulated to live. Vue is an external dependency.
|
|
514
|
-
|
|
515
|
-
Three bundles are available: pick exactly one, since importing more than one double-registers any tag they share and throws:
|
|
516
|
-
|
|
517
|
-
- `@munsonlabs/video-player/element`: everything (player + all headless controls). Auto-injects its own CSS.
|
|
518
|
-
- `@munsonlabs/video-player/element/core`: just `<ml-video-player>`/`<ml-video-stage>`/`<ml-video-card>`/`<ml-video-placeholder>`. Import `@munsonlabs/video-player/style/core` yourself.
|
|
519
|
-
- `@munsonlabs/video-player/element/controls`: just the `<ml-controls-*>` primitives. Import `@munsonlabs/video-player/style/controls` alongside it.
|
|
520
|
-
|
|
521
|
-
`/element` and `/element/core` dynamically `import()` platform adapter code only when a URL for that platform mounts (from a sibling `chunks/` directory); self-hosting either bundle means deploying `chunks/` alongside it.
|
|
522
|
-
|
|
523
|
-
### From a CDN, with no build step
|
|
524
|
-
|
|
525
|
-
```html
|
|
526
|
-
<!--
|
|
527
|
-
Vue's esm-bundler build expects its compile-time feature flags to be substituted by a
|
|
528
|
-
bundler. Nothing does that on a CDN, and `VueElement._mount` reads this one before Vue
|
|
529
|
-
can self-default it — so without this line every element registers, appears in the DOM,
|
|
530
|
-
and renders nothing, with the ReferenceError buried in `connectedCallback`.
|
|
531
|
-
|
|
532
|
-
It must be its own script: `import` is hoisted, so an assignment inside the module
|
|
533
|
-
below would run after the bundle had already mounted.
|
|
534
|
-
-->
|
|
535
|
-
<script>
|
|
536
|
-
globalThis.__VUE_PROD_DEVTOOLS__ = false
|
|
537
|
-
</script>
|
|
538
|
-
|
|
539
|
-
<script type="importmap">
|
|
540
|
-
{
|
|
541
|
-
"imports": {
|
|
542
|
-
"@munsonlabs/video-player/element": "https://esm.sh/@munsonlabs/video-player/element"
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
</script>
|
|
546
|
-
|
|
547
|
-
<script type="module">
|
|
548
|
-
import '@munsonlabs/video-player/element'
|
|
549
|
-
</script>
|
|
550
|
-
|
|
551
|
-
<ml-video-card
|
|
552
|
-
src="https://cdn.jwplayer.com/videos/O5chtspP-4VHSaSK0.mp4"
|
|
553
|
-
label="Big Buck Bunny"
|
|
554
|
-
poster="https://m.media-amazon.com/images/S/pv-target-images/fb7afef01282cdc2d846b2343f9f3d7a785b7133729776f1aa0da6501a2e1f7b.jpg"
|
|
555
|
-
></ml-video-card>
|
|
556
|
-
```
|
|
557
|
-
|
|
558
|
-
esm.sh rewrites every bare specifier in the bundle to its own URLs — `vue`, and the `hls.js`
|
|
559
|
-
and `dashjs` that an HLS or DASH source pulls in on demand — so that one global is the entire
|
|
560
|
-
setup. The import map above only aliases the package name for readability; dropping it and
|
|
561
|
-
importing the full esm.sh URL directly works just as well.
|
|
562
|
-
|
|
563
|
-
### With a bundler
|
|
564
|
-
|
|
565
|
-
None of the above applies — your bundler substitutes Vue's feature flags at build time, so
|
|
566
|
-
`import '@munsonlabs/video-player/element'` is all you need.
|
|
567
|
-
|
|
568
|
-
### Supplying your own Vue instead
|
|
569
|
-
|
|
570
|
-
If you already have Vue on the page, or want to control which build is used, mark it external
|
|
571
|
-
and map it yourself. Vue's `esm-browser` builds have the flags already substituted, so no
|
|
572
|
-
global is needed:
|
|
573
|
-
|
|
574
|
-
```html
|
|
575
|
-
<script type="importmap">
|
|
576
|
-
{
|
|
577
|
-
"imports": {
|
|
578
|
-
"vue": "https://unpkg.com/vue@3/dist/vue.runtime.esm-browser.prod.js",
|
|
579
|
-
"@munsonlabs/video-player/element": "https://esm.sh/@munsonlabs/video-player/element?external=vue"
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
</script>
|
|
583
|
-
```
|
|
584
|
-
|
|
585
|
-
Without `?external=vue`, esm.sh resolves Vue itself and a bare `vue` import map entry is
|
|
586
|
-
never consulted — it has already rewritten the specifier to its own URL.
|
|
587
|
-
|
|
588
|
-
`?external=vue` externalises **only** Vue. `hls.js` and `dashjs` stay resolved by esm.sh, so
|
|
589
|
-
nothing else changes here.
|
|
590
|
-
|
|
591
|
-
### Self-hosting, or loading raw files
|
|
592
|
-
|
|
593
|
-
Serving `dist/` yourself — or loading raw file paths from unpkg/jsdelivr rather than esm.sh —
|
|
594
|
-
gives you the bundle byte for byte, with every bare specifier intact. Nothing rewrites them, so
|
|
595
|
-
map all three yourself:
|
|
596
|
-
|
|
597
|
-
```html
|
|
598
|
-
<script type="importmap">
|
|
599
|
-
{
|
|
600
|
-
"imports": {
|
|
601
|
-
"vue": "https://unpkg.com/vue@3/dist/vue.runtime.esm-browser.prod.js",
|
|
602
|
-
"hls.js": "https://cdn.jsdelivr.net/npm/hls.js@1/+esm",
|
|
603
|
-
"dashjs": "https://cdn.jsdelivr.net/npm/dashjs@4/+esm"
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
</script>
|
|
607
|
-
```
|
|
608
|
-
|
|
609
|
-
`hls.js` and `dashjs` are each loaded on demand via a dynamic `import()` when an `.m3u8` or
|
|
610
|
-
`.mpd` source mounts, so omitting them fails at that moment rather than at load — skip them
|
|
611
|
-
only if you are certain neither format will ever play.
|
|
612
|
-
|
|
613
|
-
Platform adapter chunks are relative paths, not bare specifiers, so they need no entry — just
|
|
614
|
-
deploy `chunks/` alongside the bundle.
|
|
615
|
-
|
|
616
|
-
### Available elements
|
|
617
|
-
|
|
618
|
-
Core (in `/element` and `/element/core`):
|
|
619
|
-
|
|
620
|
-
| Element | Vue equivalent |
|
|
621
|
-
| ------------------------ | -------------------- |
|
|
622
|
-
| `<ml-video-card>` | `<VideoCard>` |
|
|
623
|
-
| `<ml-video-stage>` | `<VideoStage>` |
|
|
624
|
-
| `<ml-video-player>` | `<VideoPlayer>` |
|
|
625
|
-
| `<ml-video-placeholder>` | `<VideoPlaceholder>` |
|
|
626
|
-
| `<ml-hide-marker>` | `<HideMarker>` |
|
|
627
|
-
|
|
628
|
-
Controls (in `/element` and `/element/controls`), see [Headless Controls](#headless-controls) for what each does:
|
|
629
|
-
|
|
630
|
-
| Element | Vue equivalent |
|
|
631
|
-
| ------------------------------------ | ---------------------- |
|
|
632
|
-
| `<ml-controls-play-button>` | `<PlayButton>` |
|
|
633
|
-
| `<ml-controls-mute-button>` | `<MuteButton>` |
|
|
634
|
-
| `<ml-controls-fullscreen-button>` | `<FullscreenButton>` |
|
|
635
|
-
| `<ml-controls-loop-button>` | `<LoopButton>` |
|
|
636
|
-
| `<ml-controls-pip-button>` | `<PipButton>` |
|
|
637
|
-
| `<ml-controls-captions-button>` | `<CaptionsButton>` |
|
|
638
|
-
| `<ml-controls-quality-button>` | `<QualityButton>` |
|
|
639
|
-
| `<ml-controls-playback-rate-button>` | `<PlaybackRateButton>` |
|
|
640
|
-
| `<ml-controls-buffering>` | `<Buffering>` |
|
|
641
|
-
| `<ml-controls-scrubber>` | `<Scrubber>` |
|
|
642
|
-
| `<ml-controls-volume-slider>` | `<VolumeSlider>` |
|
|
643
|
-
| `<ml-controls-time-display>` | `<TimeDisplay>` |
|
|
644
|
-
| `<ml-controls-transcript>` | `<Transcript>` |
|
|
645
|
-
|
|
646
|
-
### Passing objects as web component attributes
|
|
647
|
-
|
|
648
|
-
Boolean and string props map directly to HTML attributes. Object props (`payload`) can be passed as inline JSON strings:
|
|
649
|
-
|
|
650
|
-
```html
|
|
651
|
-
<ml-video-card src="https://..." ad-tag-url="https://..." payload='{"articleId":"123"}'></ml-video-card>
|
|
652
|
-
```
|
|
653
|
-
|
|
654
|
-
For `action` (which contains a function), set it via JavaScript:
|
|
655
|
-
|
|
656
|
-
```js
|
|
657
|
-
const player = document.querySelector('ml-video-card')
|
|
658
|
-
player.action = { icon: '<svg.../>', label: 'Save', onClick: () => {} }
|
|
659
|
-
```
|
|
660
|
-
|
|
661
|
-
### Listening to events
|
|
662
|
-
|
|
663
|
-
```js
|
|
664
|
-
const player = document.querySelector('ml-video-card')
|
|
665
|
-
player.addEventListener('state-change', (e) => {
|
|
666
|
-
console.log(e.detail)
|
|
667
|
-
})
|
|
668
|
-
```
|
|
669
|
-
|
|
670
|
-
### Calling player methods
|
|
671
|
-
|
|
672
|
-
`<ml-video-card>`/`<ml-video-stage>` expose the same forwarded API a Vue template ref would (see [Building your own wrapper component](#building-your-own-wrapper-component)) - methods and state land directly on the element itself:
|
|
673
|
-
|
|
674
|
-
```js
|
|
675
|
-
const player = document.querySelector('ml-video-card')
|
|
676
|
-
await player.play() // resolves once playing, rejects on an autoplay block or media error
|
|
677
|
-
player.seekTo(30)
|
|
678
|
-
console.log(player.isPlaying, player.isLoaded)
|
|
679
|
-
```
|
|
680
|
-
|
|
681
|
-
### Registering a platform from a web component
|
|
682
|
-
|
|
683
|
-
[`registerPlatform`](#adding-a-custom-platform) is exported from `/element` and `/element/core` too:
|
|
684
|
-
|
|
685
|
-
```html
|
|
686
|
-
<script type="module">
|
|
687
|
-
import { registerPlatform } from 'https://esm.sh/@munsonlabs/video-player/element/core'
|
|
688
|
-
registerPlatform({ key: 'acme', test: (url) => url.includes('acme.tv'), embed: true, createAdapter: createAcmeAdapter })
|
|
689
|
-
|
|
690
|
-
const player = document.createElement('ml-video-player')
|
|
691
|
-
player.setAttribute('src', 'https://acme.tv/watch/123')
|
|
692
|
-
document.body.append(player)
|
|
693
|
-
</script>
|
|
694
|
-
```
|
|
695
|
-
|
|
696
|
-
**Timing note:** importing `/element`/`/element/core` calls `customElements.define()`, which synchronously upgrades any matching tag already in the DOM. A statically-declared `<ml-video-player>` gets upgraded before your `registerPlatform` call on the next line. Creating the tag from script, after registering, as above sidesteps this.
|
|
697
|
-
|
|
698
|
-
If your tags are statically declared, load `/element/core` with a **`?defer`** query instead: it skips the automatic `customElements.define()`, so you register your platform first and then call the exported `defineElements()` yourself:
|
|
699
|
-
|
|
700
|
-
```html
|
|
701
|
-
<script type="module">
|
|
702
|
-
import { registerPlatform, defineElements } from 'https://esm.sh/@munsonlabs/video-player/element/core?defer'
|
|
703
|
-
registerPlatform({ key: 'acme', test: (url) => url.includes('acme.tv'), embed: true, createAdapter: createAcmeAdapter })
|
|
704
|
-
defineElements()
|
|
705
|
-
</script>
|
|
706
|
-
|
|
707
|
-
<ml-video-player src="https://acme.tv/watch/123"></ml-video-player>
|
|
708
|
-
```
|
|
709
|
-
|
|
710
|
-
`?defer` is read from the `/element/core` module's own URL. `defineElements()` is idempotent: it only defines whichever tags aren't already registered.
|
|
711
|
-
|
|
712
|
-
---
|
|
28
|
+
`VideoPlayer`, `VideoStage`, headless controls, and web component (`ml-video-*`) exports are also available - see the docs for the full component list.
|
|
713
29
|
|
|
714
|
-
##
|
|
30
|
+
## Docs
|
|
715
31
|
|
|
716
|
-
|
|
717
|
-
| -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
718
|
-
| `vue` | `>=3` (Vue usage only) |
|
|
719
|
-
| `hls.js` | `^1.6.16`, optional, only needed for HLS (`.m3u8`) playback via native `<video>`; see [Web Component Usage](#web-component-usage) for the import map entry if you're not using a bundler |
|
|
720
|
-
| `dashjs` | `^4.7.4`, optional, only needed for DASH (`.mpd`) playback via native `<video>`; see [Web Component Usage](#web-component-usage) for the import map entry if you're not using a bundler |
|
|
32
|
+
Full guides for props, events, ads, captions, quality, theming, headless controls, and web component usage: **https://munsonlabs.github.io/packages/video-player/getting-started/introduction**
|