ts-video-player 0.1.9 → 0.1.10
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/dist/cdn.min.js +25 -25
- package/dist/{chunk-fvxawgp5.js → chunk-cay3ahqr.js} +29 -4
- package/dist/elements/index.js +1 -1
- package/dist/index.js +1 -1
- package/dist/providers/base.d.ts +12 -0
- package/package.json +1 -1
- package/src/player.ts +7 -1
- package/src/providers/base.ts +34 -1
- package/src/providers/hls.ts +5 -0
- package/src/providers/html5.ts +5 -0
- package/src/providers/vimeo.ts +1 -2
- package/src/providers/youtube.ts +1 -0
|
@@ -148,6 +148,8 @@ class BaseProvider2 {
|
|
|
148
148
|
}
|
|
149
149
|
destroy() {
|
|
150
150
|
this._ready = false;
|
|
151
|
+
this.stopEmbedFullscreen?.();
|
|
152
|
+
this.stopEmbedFullscreen = null;
|
|
151
153
|
this.cleanup();
|
|
152
154
|
this.events.removeAllListeners();
|
|
153
155
|
}
|
|
@@ -167,6 +169,25 @@ class BaseProvider2 {
|
|
|
167
169
|
return [];
|
|
168
170
|
}
|
|
169
171
|
setAudioTrack(_trackId) {}
|
|
172
|
+
stopEmbedFullscreen = null;
|
|
173
|
+
announceEmbedFeatures() {
|
|
174
|
+
if (typeof document === "undefined" || typeof HTMLMediaElement === "undefined")
|
|
175
|
+
return;
|
|
176
|
+
const fullscreenAvailability = this.getFeatureAvailability("fullscreen");
|
|
177
|
+
const pipAvailability = this.getFeatureAvailability("pip");
|
|
178
|
+
const volumeAvailability = this.getFeatureAvailability("volume");
|
|
179
|
+
this.events.emit("statechange", {
|
|
180
|
+
canFullscreen: fullscreenAvailability === "available",
|
|
181
|
+
canPictureInPicture: pipAvailability === "available",
|
|
182
|
+
fullscreenAvailability,
|
|
183
|
+
pipAvailability,
|
|
184
|
+
volumeAvailability
|
|
185
|
+
});
|
|
186
|
+
this.events.emit("availabilitychange", "fullscreen", fullscreenAvailability);
|
|
187
|
+
this.events.emit("availabilitychange", "pip", pipAvailability);
|
|
188
|
+
this.events.emit("availabilitychange", "volume", volumeAvailability);
|
|
189
|
+
this.stopEmbedFullscreen ??= onFullscreenChange2((fullscreen) => this.events.emit("fullscreenchange", fullscreen));
|
|
190
|
+
}
|
|
170
191
|
getFeatureAvailability(feature) {
|
|
171
192
|
const el = this.mediaElement;
|
|
172
193
|
const media = el instanceof HTMLMediaElement ? el : null;
|
|
@@ -267,6 +288,8 @@ class HTML5Provider2 extends BaseProvider2 {
|
|
|
267
288
|
this.isAudio = this.options.mediaType === "audio";
|
|
268
289
|
this.media = document.createElement(this.isAudio ? "audio" : "video");
|
|
269
290
|
this.media.className = "ts-video-player__media";
|
|
291
|
+
if (!this.isAudio)
|
|
292
|
+
this.media.style.cssText = "display:block;width:100%;height:100%;object-fit:contain;background:#000";
|
|
270
293
|
if (this.options.autoplay)
|
|
271
294
|
this.media.autoplay = true;
|
|
272
295
|
if (this.options.loop)
|
|
@@ -715,6 +738,7 @@ class YouTubeProvider2 extends BaseProvider2 {
|
|
|
715
738
|
}, 250);
|
|
716
739
|
this.events.emit("loadedmetadata");
|
|
717
740
|
this.events.emit("canplay");
|
|
741
|
+
this.announceEmbedFeatures();
|
|
718
742
|
if (this.pendingVideoId) {
|
|
719
743
|
const videoId = this.pendingVideoId;
|
|
720
744
|
this.pendingVideoId = null;
|
|
@@ -1066,10 +1090,9 @@ class VimeoProvider2 extends BaseProvider2 {
|
|
|
1066
1090
|
this.events.emit("statechange", {
|
|
1067
1091
|
videoWidth: width,
|
|
1068
1092
|
videoHeight: height,
|
|
1069
|
-
aspectRatio: width / height || 16 / 9
|
|
1070
|
-
canFullscreen: true,
|
|
1071
|
-
canPictureInPicture: false
|
|
1093
|
+
aspectRatio: width / height || 16 / 9
|
|
1072
1094
|
});
|
|
1095
|
+
this.announceEmbedFeatures();
|
|
1073
1096
|
}
|
|
1074
1097
|
attachVimeoEvents() {
|
|
1075
1098
|
if (!this.player)
|
|
@@ -1279,6 +1302,7 @@ class HLSProvider2 extends BaseProvider2 {
|
|
|
1279
1302
|
}
|
|
1280
1303
|
this.video = document.createElement("video");
|
|
1281
1304
|
this.video.className = "ts-video-player__media";
|
|
1305
|
+
this.video.style.cssText = "display:block;width:100%;height:100%;object-fit:contain;background:#000";
|
|
1282
1306
|
if (this.options.autoplay)
|
|
1283
1307
|
this.video.autoplay = true;
|
|
1284
1308
|
if (this.options.loop)
|
|
@@ -3241,7 +3265,8 @@ class Player2 {
|
|
|
3241
3265
|
if (this.state.pictureInPicture) {
|
|
3242
3266
|
await this.exitPiP();
|
|
3243
3267
|
}
|
|
3244
|
-
|
|
3268
|
+
const media = this._provider?.mediaElement;
|
|
3269
|
+
await enterFullscreen2(this._el, media instanceof HTMLMediaElement ? media : null);
|
|
3245
3270
|
if (this._options.fullscreenOrientationLock !== false) {
|
|
3246
3271
|
lockOrientation2("landscape");
|
|
3247
3272
|
}
|
package/dist/elements/index.js
CHANGED
package/dist/index.js
CHANGED
package/dist/providers/base.d.ts
CHANGED
|
@@ -43,6 +43,18 @@ export declare abstract class BaseProvider implements Provider {
|
|
|
43
43
|
setTextTrackMode(_trackId: string, _mode: 'disabled' | 'hidden' | 'showing'): void;
|
|
44
44
|
getAudioTracks(): AudioTrack[];
|
|
45
45
|
setAudioTrack(_trackId: string): void;
|
|
46
|
+
private stopEmbedFullscreen;
|
|
47
|
+
/**
|
|
48
|
+
* What an embedded player (YouTube, Vimeo) can do, said once it is ready.
|
|
49
|
+
*
|
|
50
|
+
* The native-media providers say it on loadedmetadata; the embeds said
|
|
51
|
+
* nothing, so fullscreen stayed "unavailable" and its button disabled,
|
|
52
|
+
* though the player can fill the screen like any other. Fullscreen is the
|
|
53
|
+
* container's own (the player's controls come along); picture in picture
|
|
54
|
+
* is the embed's business. Also follows the document into and out of
|
|
55
|
+
* fullscreen, which only the native providers did.
|
|
56
|
+
*/
|
|
57
|
+
protected announceEmbedFeatures(): void;
|
|
46
58
|
getFeatureAvailability(feature: 'volume' | 'fullscreen' | 'pip'): FeatureAvailability;
|
|
47
59
|
enterFullscreen(): Promise<void>;
|
|
48
60
|
exitFullscreen(): Promise<void>;
|
package/package.json
CHANGED
package/src/player.ts
CHANGED
|
@@ -22,6 +22,7 @@ import { createMediaSession, type MediaSessionOptions } from './core/media-sessi
|
|
|
22
22
|
import { lockOrientation, unlockOrientation } from './core/orientation'
|
|
23
23
|
import { defaultLoaders, findSourceCandidates } from './providers'
|
|
24
24
|
import { embedCover } from './core/cover'
|
|
25
|
+
import { enterFullscreen as enterFullscreenElement } from './core/features'
|
|
25
26
|
import { label } from './elements/utils'
|
|
26
27
|
|
|
27
28
|
// =============================================================================
|
|
@@ -799,7 +800,12 @@ export class Player implements IPlayer {
|
|
|
799
800
|
if (this.state.pictureInPicture) {
|
|
800
801
|
await this.exitPiP()
|
|
801
802
|
}
|
|
802
|
-
|
|
803
|
+
// The whole player, controls included: the provider's own container holds
|
|
804
|
+
// only the media, so a fullscreen video had no controls to pause or seek
|
|
805
|
+
// with. A browser that can only take the <video> itself fullscreen (iOS
|
|
806
|
+
// Safari) still gets it, with the system's controls.
|
|
807
|
+
const media = this._provider?.mediaElement
|
|
808
|
+
await enterFullscreenElement(this._el, media instanceof HTMLMediaElement ? media : null)
|
|
803
809
|
if (this._options.fullscreenOrientationLock !== false) {
|
|
804
810
|
lockOrientation('landscape')
|
|
805
811
|
}
|
package/src/providers/base.ts
CHANGED
|
@@ -18,7 +18,7 @@ import type {
|
|
|
18
18
|
MediaError,
|
|
19
19
|
FeatureAvailability,
|
|
20
20
|
} from '../types'
|
|
21
|
-
import { EventEmitter } from '../core/events'
|
|
21
|
+
import { EventEmitter, onFullscreenChange } from '../core/events'
|
|
22
22
|
import {
|
|
23
23
|
detectVolumeAvailability,
|
|
24
24
|
detectFullscreenAvailability,
|
|
@@ -63,6 +63,8 @@ export abstract class BaseProvider implements Provider {
|
|
|
63
63
|
|
|
64
64
|
destroy(): void {
|
|
65
65
|
this._ready = false
|
|
66
|
+
this.stopEmbedFullscreen?.()
|
|
67
|
+
this.stopEmbedFullscreen = null
|
|
66
68
|
this.cleanup()
|
|
67
69
|
this.events.removeAllListeners()
|
|
68
70
|
}
|
|
@@ -132,6 +134,37 @@ export abstract class BaseProvider implements Provider {
|
|
|
132
134
|
|
|
133
135
|
// === Feature Availability ===
|
|
134
136
|
|
|
137
|
+
private stopEmbedFullscreen: (() => void) | null = null
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* What an embedded player (YouTube, Vimeo) can do, said once it is ready.
|
|
141
|
+
*
|
|
142
|
+
* The native-media providers say it on loadedmetadata; the embeds said
|
|
143
|
+
* nothing, so fullscreen stayed "unavailable" and its button disabled,
|
|
144
|
+
* though the player can fill the screen like any other. Fullscreen is the
|
|
145
|
+
* container's own (the player's controls come along); picture in picture
|
|
146
|
+
* is the embed's business. Also follows the document into and out of
|
|
147
|
+
* fullscreen, which only the native providers did.
|
|
148
|
+
*/
|
|
149
|
+
protected announceEmbedFeatures(): void {
|
|
150
|
+
// Nothing to measure without a page (server rendering, tests).
|
|
151
|
+
if (typeof document === 'undefined' || typeof HTMLMediaElement === 'undefined') return
|
|
152
|
+
const fullscreenAvailability = this.getFeatureAvailability('fullscreen')
|
|
153
|
+
const pipAvailability = this.getFeatureAvailability('pip')
|
|
154
|
+
const volumeAvailability = this.getFeatureAvailability('volume')
|
|
155
|
+
this.events.emit('statechange', {
|
|
156
|
+
canFullscreen: fullscreenAvailability === 'available',
|
|
157
|
+
canPictureInPicture: pipAvailability === 'available',
|
|
158
|
+
fullscreenAvailability,
|
|
159
|
+
pipAvailability,
|
|
160
|
+
volumeAvailability,
|
|
161
|
+
})
|
|
162
|
+
this.events.emit('availabilitychange', 'fullscreen', fullscreenAvailability)
|
|
163
|
+
this.events.emit('availabilitychange', 'pip', pipAvailability)
|
|
164
|
+
this.events.emit('availabilitychange', 'volume', volumeAvailability)
|
|
165
|
+
this.stopEmbedFullscreen ??= onFullscreenChange(fullscreen => this.events.emit('fullscreenchange', fullscreen))
|
|
166
|
+
}
|
|
167
|
+
|
|
135
168
|
getFeatureAvailability(feature: 'volume' | 'fullscreen' | 'pip'): FeatureAvailability {
|
|
136
169
|
// Only pass media element if it's actually an HTMLMediaElement (not an iframe)
|
|
137
170
|
const el = this.mediaElement
|
package/src/providers/hls.ts
CHANGED
|
@@ -207,6 +207,11 @@ export class HLSProvider extends BaseProvider {
|
|
|
207
207
|
// Create video element
|
|
208
208
|
this.video = document.createElement('video')
|
|
209
209
|
this.video.className = 'ts-video-player__media'
|
|
210
|
+
// Sized here: inside <video-player>'s shadow root no page stylesheet
|
|
211
|
+
// reaches it, and unsized a <video> shows at its own resolution, clipped
|
|
212
|
+
// by the player (a 1280px clip in a 1024px dialog lost its edges, and
|
|
213
|
+
// in fullscreen it sat in a corner). Letterboxed, never cropped.
|
|
214
|
+
this.video.style.cssText = 'display:block;width:100%;height:100%;object-fit:contain;background:#000'
|
|
210
215
|
|
|
211
216
|
// Set attributes
|
|
212
217
|
if (this.options.autoplay) this.video.autoplay = true
|
package/src/providers/html5.ts
CHANGED
|
@@ -114,6 +114,11 @@ export class HTML5Provider extends BaseProvider {
|
|
|
114
114
|
// Create media element
|
|
115
115
|
this.media = document.createElement(this.isAudio ? 'audio' : 'video')
|
|
116
116
|
this.media.className = 'ts-video-player__media'
|
|
117
|
+
// Sized here: inside <video-player>'s shadow root no page stylesheet
|
|
118
|
+
// reaches it, and unsized a <video> shows at its own resolution, clipped
|
|
119
|
+
// by the player (a 1280px clip in a 1024px dialog lost its edges, and
|
|
120
|
+
// in fullscreen it sat in a corner). Letterboxed, never cropped.
|
|
121
|
+
if (!this.isAudio) this.media.style.cssText = 'display:block;width:100%;height:100%;object-fit:contain;background:#000'
|
|
117
122
|
|
|
118
123
|
// Set attributes
|
|
119
124
|
if (this.options.autoplay) this.media.autoplay = true
|
package/src/providers/vimeo.ts
CHANGED
|
@@ -310,9 +310,8 @@ export class VimeoProvider extends BaseProvider {
|
|
|
310
310
|
videoWidth: width,
|
|
311
311
|
videoHeight: height,
|
|
312
312
|
aspectRatio: width / height || 16 / 9,
|
|
313
|
-
canFullscreen: true,
|
|
314
|
-
canPictureInPicture: false,
|
|
315
313
|
})
|
|
314
|
+
this.announceEmbedFeatures()
|
|
316
315
|
}
|
|
317
316
|
|
|
318
317
|
private attachVimeoEvents(): void {
|
package/src/providers/youtube.ts
CHANGED