@symbiote-native/video 0.0.0-stage → 0.0.0
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/LICENSE +21 -0
- package/README.md +67 -2
- package/build/angular/index.d.ts +4 -0
- package/build/angular/index.js +5 -0
- package/build/angular/inject-video-player.d.ts +4 -0
- package/build/angular/inject-video-player.js +6 -0
- package/build/angular/video-airplay-button/index.d.ts +12 -0
- package/build/angular/video-airplay-button/index.js +78 -0
- package/build/angular/video-view/index.d.ts +17 -0
- package/build/angular/video-view/index.js +97 -0
- package/build/core/constants.d.ts +12 -0
- package/build/core/constants.js +13 -0
- package/build/core/index.d.ts +10 -0
- package/build/core/index.js +8 -0
- package/build/core/native-module.d.ts +10 -0
- package/build/core/native-module.js +3 -0
- package/build/core/player-types.d.ts +221 -0
- package/build/core/player-types.js +1 -0
- package/build/core/video-airplay-button.d.ts +4 -0
- package/build/core/video-airplay-button.js +29 -0
- package/build/core/video-module.d.ts +12 -0
- package/build/core/video-module.js +21 -0
- package/build/core/video-player-controller.d.ts +15 -0
- package/build/core/video-player-controller.js +21 -0
- package/build/core/video-player.d.ts +7 -0
- package/build/core/video-player.js +22 -0
- package/build/core/video-source.d.ts +3 -0
- package/build/core/video-source.js +20 -0
- package/build/core/video-thumbnail.d.ts +4 -0
- package/build/core/video-thumbnail.js +3 -0
- package/build/core/video-view.d.ts +18 -0
- package/build/core/video-view.js +63 -0
- package/build/core/view-types.d.ts +90 -0
- package/build/core/view-types.js +1 -0
- package/build/react/index.d.ts +4 -0
- package/build/react/index.js +4 -0
- package/build/react/use-video-player.d.ts +7 -0
- package/build/react/use-video-player.js +11 -0
- package/build/react/video-view.d.ts +13 -0
- package/build/react/video-view.js +10 -0
- package/build/solid/index.d.ts +3 -0
- package/build/solid/index.js +4 -0
- package/build/solid/use-video-player.d.ts +8 -0
- package/build/solid/use-video-player.js +11 -0
- package/build/solid/video-view.d.ts +33 -0
- package/build/solid/video-view.js +6 -0
- package/build/svelte/index.d.ts +5 -0
- package/build/svelte/index.js +5 -0
- package/build/svelte/use-video-player.svelte.d.ts +10 -0
- package/build/svelte/use-video-player.svelte.js +11 -0
- package/build/svelte/video-airplay-button.svelte +12 -0
- package/build/svelte/video-airplay-button.svelte.d.ts +4 -0
- package/build/svelte/video-view.svelte +25 -0
- package/build/svelte/video-view.svelte.d.ts +9 -0
- package/build/vue/index.d.ts +3 -0
- package/build/vue/index.js +4 -0
- package/build/vue/use-video-player.d.ts +8 -0
- package/build/vue/use-video-player.js +16 -0
- package/build/vue/video-view.d.ts +5 -0
- package/build/vue/video-view.js +14 -0
- package/build-ngc/angular/index.d.ts +4 -0
- package/build-ngc/angular/index.js +6 -0
- package/build-ngc/angular/index.js.map +1 -0
- package/build-ngc/angular/inject-video-player.d.ts +4 -0
- package/build-ngc/angular/inject-video-player.js +7 -0
- package/build-ngc/angular/inject-video-player.js.map +1 -0
- package/build-ngc/angular/video-airplay-button/index.d.ts +15 -0
- package/build-ngc/angular/video-airplay-button/index.js +37 -0
- package/build-ngc/angular/video-airplay-button/index.js.map +1 -0
- package/build-ngc/angular/video-view/index.d.ts +20 -0
- package/build-ngc/angular/video-view/index.js +56 -0
- package/build-ngc/angular/video-view/index.js.map +1 -0
- package/build-ngc/core/constants.d.ts +12 -0
- package/build-ngc/core/constants.js +14 -0
- package/build-ngc/core/constants.js.map +1 -0
- package/build-ngc/core/index.d.ts +10 -0
- package/build-ngc/core/index.js +9 -0
- package/build-ngc/core/index.js.map +1 -0
- package/build-ngc/core/native-module.d.ts +10 -0
- package/build-ngc/core/native-module.js +4 -0
- package/build-ngc/core/native-module.js.map +1 -0
- package/build-ngc/core/player-types.d.ts +221 -0
- package/build-ngc/core/player-types.js +2 -0
- package/build-ngc/core/player-types.js.map +1 -0
- package/build-ngc/core/video-airplay-button.d.ts +4 -0
- package/build-ngc/core/video-airplay-button.js +30 -0
- package/build-ngc/core/video-airplay-button.js.map +1 -0
- package/build-ngc/core/video-module.d.ts +12 -0
- package/build-ngc/core/video-module.js +22 -0
- package/build-ngc/core/video-module.js.map +1 -0
- package/build-ngc/core/video-player-controller.d.ts +15 -0
- package/build-ngc/core/video-player-controller.js +22 -0
- package/build-ngc/core/video-player-controller.js.map +1 -0
- package/build-ngc/core/video-player.d.ts +7 -0
- package/build-ngc/core/video-player.js +23 -0
- package/build-ngc/core/video-player.js.map +1 -0
- package/build-ngc/core/video-source.d.ts +3 -0
- package/build-ngc/core/video-source.js +21 -0
- package/build-ngc/core/video-source.js.map +1 -0
- package/build-ngc/core/video-thumbnail.d.ts +4 -0
- package/build-ngc/core/video-thumbnail.js +4 -0
- package/build-ngc/core/video-thumbnail.js.map +1 -0
- package/build-ngc/core/video-view.d.ts +18 -0
- package/build-ngc/core/video-view.js +64 -0
- package/build-ngc/core/video-view.js.map +1 -0
- package/build-ngc/core/view-types.d.ts +90 -0
- package/build-ngc/core/view-types.js +2 -0
- package/build-ngc/core/view-types.js.map +1 -0
- package/native-link.json +31 -0
- package/package.json +144 -3
- package/src/angular/index.ts +6 -0
- package/src/angular/inject-video-player.ts +20 -0
- package/src/angular/video-airplay-button/index.ts +39 -0
- package/src/angular/video-view/index.ts +58 -0
- package/src/core/constants.ts +16 -0
- package/src/core/index.ts +56 -0
- package/src/core/native-module.ts +16 -0
- package/src/core/player-types.ts +245 -0
- package/src/core/video-airplay-button.ts +45 -0
- package/src/core/video-module.ts +25 -0
- package/src/core/video-player-controller.ts +50 -0
- package/src/core/video-player.ts +38 -0
- package/src/core/video-source.ts +21 -0
- package/src/core/video-thumbnail.ts +7 -0
- package/src/core/video-view.ts +107 -0
- package/src/core/view-types.ts +114 -0
- package/src/react/index.ts +6 -0
- package/src/react/use-video-player.ts +22 -0
- package/src/react/video-view.ts +33 -0
- package/src/solid/index.ts +5 -0
- package/src/solid/use-video-player.ts +24 -0
- package/src/solid/video-view.ts +22 -0
- package/src/svelte/index.ts +7 -0
- package/src/svelte/svelte-ambient.d.ts +3 -0
- package/src/svelte/svelte-compile.test-helper.ts +6 -0
- package/src/svelte/use-video-player.svelte.ts +27 -0
- package/src/svelte/video-airplay-button.svelte +12 -0
- package/src/svelte/video-view.svelte +25 -0
- package/src/vue/index.ts +5 -0
- package/src/vue/use-video-player.ts +31 -0
- package/src/vue/video-view.ts +21 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 A. Prokopenko
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,68 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @symbiote-native/video
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[`expo-video`](https://docs.expo.dev/versions/latest/sdk/video/) for **every**
|
|
4
|
+
[SymbioteNative](../../README.md) adapter: React, Vue, Svelte, Solid and Angular. The player, the
|
|
5
|
+
view, thumbnails, the video cache and the AirPlay button.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx @symbiote-native/cli new my-app --video # new app
|
|
11
|
+
npx @symbiote-native/cli add --video # existing app
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Manual: `npm install @symbiote-native/video`, then wire `expo-modules-autolinking` once per app.
|
|
15
|
+
Never install `expo-video` or the `expo` meta-package yourself.
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import { VideoView, useVideoPlayer } from '@symbiote-native/video/react';
|
|
21
|
+
|
|
22
|
+
const player = useVideoPlayer('https://example.com/video.mp4', player => {
|
|
23
|
+
player.loop = true;
|
|
24
|
+
player.play();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
<VideoView player={player} style={{ width: 320, height: 180 }} nativeControls />;
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`createVideoPlayer(source, builderOptions?)` makes a player that is not released by itself, call
|
|
31
|
+
`player.release()`. The player is the native `VideoPlayer` object: its properties, `play()`,
|
|
32
|
+
`pause()`, `replaceAsync()`, `generateThumbnailsAsync()` and `addListener(event, listener)` are
|
|
33
|
+
the same on every adapter. The player is created by the hook of the framework, which releases it
|
|
34
|
+
when the owner goes away:
|
|
35
|
+
|
|
36
|
+
| Adapter | Player |
|
|
37
|
+
| ------- | ----------------------------------------------------------------------- |
|
|
38
|
+
| React | `useVideoPlayer(source, setup?, options?)` |
|
|
39
|
+
| Vue | `useVideoPlayer(source, setup?, options?)`, returns a computed ref |
|
|
40
|
+
| Solid | `useVideoPlayer(() => source, setup?, () => options)`, returns an accessor |
|
|
41
|
+
| Svelte | `useVideoPlayer(() => source, setup?, () => options).current` |
|
|
42
|
+
| Angular | `injectVideoPlayer(() => source, setup?, () => options)`, a signal |
|
|
43
|
+
|
|
44
|
+
`setup` runs once on each new player, and a changed source or options make a new one.
|
|
45
|
+
|
|
46
|
+
The functions of the view (`enterFullscreen`, `exitFullscreen`, `startPictureInPicture`,
|
|
47
|
+
`stopPictureInPicture`) are reached by a `ref` in React and Solid (`ref={fn}`), the exposed members
|
|
48
|
+
of a template ref in Vue, `bind:this` in Svelte and `@ViewChild` in Angular.
|
|
49
|
+
|
|
50
|
+
Module functions: `isPictureInPictureSupported()`, `setVideoCacheSizeAsync(bytes)`,
|
|
51
|
+
`clearVideoCacheAsync()`, `getCurrentVideoCacheSize()`. `VideoThumbnail` is the native class of the
|
|
52
|
+
thumbnails. `VideoAirPlayButton` is the iOS route picker and a plain view elsewhere.
|
|
53
|
+
|
|
54
|
+
## App setup
|
|
55
|
+
|
|
56
|
+
Nothing is written by default. Background playback and Picture in Picture need what upstream's
|
|
57
|
+
config plugin adds:
|
|
58
|
+
|
|
59
|
+
- iOS: add `audio` to `UIBackgroundModes` in `Info.plist`.
|
|
60
|
+
- Android: pick the "Background video playback" bundle for the foreground service and its
|
|
61
|
+
permissions. Picture in Picture needs `android:supportsPictureInPicture="true"` on the main
|
|
62
|
+
activity.
|
|
63
|
+
|
|
64
|
+
## Not ported
|
|
65
|
+
|
|
66
|
+
The web build. `useEvent` / `useEventListener` ship with the `expo` package upstream, here the
|
|
67
|
+
adapters export them for any emitter (`useEvent(player, 'statusChange')`, `injectEvent` in
|
|
68
|
+
Angular, `@symbiote-native/svelte/runes/use-event` in Svelte). Upstream ships no tests, the suites here are written against its behavior.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// @symbiote-native/video/angular: плеер, view, миниатюры, кэш и кнопка AirPlay на общем ядре
|
|
2
|
+
export { VideoView } from './video-view/index.js';
|
|
3
|
+
export { VideoAirPlayButton } from './video-airplay-button/index.js';
|
|
4
|
+
export { injectVideoPlayer } from './inject-video-player.js';
|
|
5
|
+
export * from '../core/index.js';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Signal } from '@angular/core';
|
|
2
|
+
import type { IVideoPlayerBuilderOptions, IVideoSource, VideoPlayer } from '../core';
|
|
3
|
+
/** Released with the injection context, `setup` runs once on each new player */
|
|
4
|
+
export declare const injectVideoPlayer: (source: () => IVideoSource, setup?: (player: VideoPlayer) => void, options?: () => IVideoPlayerBuilderOptions | undefined) => Signal<VideoPlayer>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Angular twin of `useVideoPlayer`, `injectX` shape matching `injectAudioPlayer`
|
|
2
|
+
import { createResourceHook } from '@symbiote-native/angular';
|
|
3
|
+
import { createVideoPlayerController } from '../core/video-player-controller.js';
|
|
4
|
+
const injectPlayerResource = createResourceHook(createVideoPlayerController);
|
|
5
|
+
/** Released with the injection context, `setup` runs once on each new player */
|
|
6
|
+
export const injectVideoPlayer = (source, setup, options) => injectPlayerResource(() => [source(), options?.(), setup]);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { NativeViewBase } from '@symbiote-native/angular';
|
|
2
|
+
import type { IDescriptor } from '@symbiote-native/components';
|
|
3
|
+
import type { IVideoAirPlayButtonProps } from '../../core';
|
|
4
|
+
declare const AIRPLAY_INPUTS: readonly ["tint", "activeTint", "prioritizeVideoDevices", "onBeginPresentingRoutes", "onEndPresentingRoutes"];
|
|
5
|
+
export interface VideoAirPlayButton extends Partial<Pick<IVideoAirPlayButtonProps, (typeof AIRPLAY_INPUTS)[number]>> {
|
|
6
|
+
}
|
|
7
|
+
/** Angular twin of `expo-video`'s `VideoAirPlayButton`, a plain view off iOS */
|
|
8
|
+
export declare class VideoAirPlayButton extends NativeViewBase {
|
|
9
|
+
protected readonly propNames: readonly ["tint", "activeTint", "prioritizeVideoDevices", "onBeginPresentingRoutes", "onEndPresentingRoutes"];
|
|
10
|
+
protected renderView(props: object): IDescriptor | null;
|
|
11
|
+
}
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
2
|
+
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
3
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
4
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
5
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
6
|
+
var _, done = false;
|
|
7
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
8
|
+
var context = {};
|
|
9
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
10
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
11
|
+
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
12
|
+
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
13
|
+
if (kind === "accessor") {
|
|
14
|
+
if (result === void 0) continue;
|
|
15
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
16
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
17
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
18
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
19
|
+
}
|
|
20
|
+
else if (_ = accept(result)) {
|
|
21
|
+
if (kind === "field") initializers.unshift(_);
|
|
22
|
+
else descriptor[key] = _;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
26
|
+
done = true;
|
|
27
|
+
};
|
|
28
|
+
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
29
|
+
var useValue = arguments.length > 2;
|
|
30
|
+
for (var i = 0; i < initializers.length; i++) {
|
|
31
|
+
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
32
|
+
}
|
|
33
|
+
return useValue ? value : void 0;
|
|
34
|
+
};
|
|
35
|
+
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
|
36
|
+
import { DescriptorOutlet, NativeViewBase } from '@symbiote-native/angular';
|
|
37
|
+
import { renderVideoAirPlayButton } from '../../core/index.js';
|
|
38
|
+
// One list is the inputs of the component and the props it hands to the native view
|
|
39
|
+
const AIRPLAY_INPUTS = [
|
|
40
|
+
'tint',
|
|
41
|
+
'activeTint',
|
|
42
|
+
'prioritizeVideoDevices',
|
|
43
|
+
'onBeginPresentingRoutes',
|
|
44
|
+
'onEndPresentingRoutes',
|
|
45
|
+
];
|
|
46
|
+
/** Angular twin of `expo-video`'s `VideoAirPlayButton`, a plain view off iOS */
|
|
47
|
+
let VideoAirPlayButton = (() => {
|
|
48
|
+
let _classDecorators = [Component({
|
|
49
|
+
selector: 'VideoAirPlayButton',
|
|
50
|
+
standalone: true,
|
|
51
|
+
imports: [DescriptorOutlet],
|
|
52
|
+
inputs: [...AIRPLAY_INPUTS],
|
|
53
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
54
|
+
template: `@if (descriptor; as node) {
|
|
55
|
+
<symbiote-descriptor-outlet [node]="node" />
|
|
56
|
+
}`,
|
|
57
|
+
})];
|
|
58
|
+
let _classDescriptor;
|
|
59
|
+
let _classExtraInitializers = [];
|
|
60
|
+
let _classThis;
|
|
61
|
+
let _classSuper = NativeViewBase;
|
|
62
|
+
var VideoAirPlayButton = class extends _classSuper {
|
|
63
|
+
static { _classThis = this; }
|
|
64
|
+
static {
|
|
65
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
66
|
+
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
67
|
+
VideoAirPlayButton = _classThis = _classDescriptor.value;
|
|
68
|
+
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
69
|
+
__runInitializers(_classThis, _classExtraInitializers);
|
|
70
|
+
}
|
|
71
|
+
propNames = AIRPLAY_INPUTS;
|
|
72
|
+
renderView(props) {
|
|
73
|
+
return renderVideoAirPlayButton(props);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
return VideoAirPlayButton = _classThis;
|
|
77
|
+
})();
|
|
78
|
+
export { VideoAirPlayButton };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { NativeViewBase } from '@symbiote-native/angular';
|
|
2
|
+
import type { IDescriptor } from '@symbiote-native/components';
|
|
3
|
+
import type { IVideoViewProps } from '../../core';
|
|
4
|
+
declare const VIDEO_VIEW_INPUTS: readonly ["player", "nativeControls", "contentFit", "fullscreenOptions", "showsTimecodes", "requiresLinearPlayback", "buttonOptions", "surfaceType", "contentPosition", "onPictureInPictureStart", "onPictureInPictureStop", "allowsPictureInPicture", "startsPictureInPictureAutomatically", "allowsVideoFrameAnalysis", "onFullscreenEnter", "onFullscreenExit", "onFirstFrameRender", "useExoShutter"];
|
|
5
|
+
export interface VideoView extends Partial<Pick<IVideoViewProps, (typeof VIDEO_VIEW_INPUTS)[number]>> {
|
|
6
|
+
}
|
|
7
|
+
/** Angular twin of `expo-video`'s `VideoView` */
|
|
8
|
+
export declare class VideoView extends NativeViewBase {
|
|
9
|
+
private readonly view;
|
|
10
|
+
readonly enterFullscreen: () => Promise<void>;
|
|
11
|
+
readonly exitFullscreen: () => Promise<void>;
|
|
12
|
+
readonly startPictureInPicture: () => Promise<void>;
|
|
13
|
+
readonly stopPictureInPicture: () => Promise<void>;
|
|
14
|
+
protected readonly propNames: readonly ["player", "nativeControls", "contentFit", "fullscreenOptions", "showsTimecodes", "requiresLinearPlayback", "buttonOptions", "surfaceType", "contentPosition", "onPictureInPictureStart", "onPictureInPictureStop", "allowsPictureInPicture", "startsPictureInPictureAutomatically", "allowsVideoFrameAnalysis", "onFullscreenEnter", "onFullscreenExit", "onFirstFrameRender", "useExoShutter"];
|
|
15
|
+
protected renderView(props: object): IDescriptor | null;
|
|
16
|
+
}
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
2
|
+
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
3
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
4
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
5
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
6
|
+
var _, done = false;
|
|
7
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
8
|
+
var context = {};
|
|
9
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
10
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
11
|
+
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
12
|
+
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
13
|
+
if (kind === "accessor") {
|
|
14
|
+
if (result === void 0) continue;
|
|
15
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
16
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
17
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
18
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
19
|
+
}
|
|
20
|
+
else if (_ = accept(result)) {
|
|
21
|
+
if (kind === "field") initializers.unshift(_);
|
|
22
|
+
else descriptor[key] = _;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
26
|
+
done = true;
|
|
27
|
+
};
|
|
28
|
+
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
29
|
+
var useValue = arguments.length > 2;
|
|
30
|
+
for (var i = 0; i < initializers.length; i++) {
|
|
31
|
+
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
32
|
+
}
|
|
33
|
+
return useValue ? value : void 0;
|
|
34
|
+
};
|
|
35
|
+
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
|
36
|
+
import { DescriptorOutlet, NativeViewBase } from '@symbiote-native/angular';
|
|
37
|
+
import { createVideoView } from '../../core/index.js';
|
|
38
|
+
// One list is the inputs of the component and the props it hands to the native view
|
|
39
|
+
const VIDEO_VIEW_INPUTS = [
|
|
40
|
+
'player',
|
|
41
|
+
'nativeControls',
|
|
42
|
+
'contentFit',
|
|
43
|
+
'fullscreenOptions',
|
|
44
|
+
'showsTimecodes',
|
|
45
|
+
'requiresLinearPlayback',
|
|
46
|
+
'buttonOptions',
|
|
47
|
+
'surfaceType',
|
|
48
|
+
'contentPosition',
|
|
49
|
+
'onPictureInPictureStart',
|
|
50
|
+
'onPictureInPictureStop',
|
|
51
|
+
'allowsPictureInPicture',
|
|
52
|
+
'startsPictureInPictureAutomatically',
|
|
53
|
+
'allowsVideoFrameAnalysis',
|
|
54
|
+
'onFullscreenEnter',
|
|
55
|
+
'onFullscreenExit',
|
|
56
|
+
'onFirstFrameRender',
|
|
57
|
+
'useExoShutter',
|
|
58
|
+
];
|
|
59
|
+
/** Angular twin of `expo-video`'s `VideoView` */
|
|
60
|
+
let VideoView = (() => {
|
|
61
|
+
let _classDecorators = [Component({
|
|
62
|
+
selector: 'VideoView',
|
|
63
|
+
standalone: true,
|
|
64
|
+
imports: [DescriptorOutlet],
|
|
65
|
+
inputs: [...VIDEO_VIEW_INPUTS],
|
|
66
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
67
|
+
template: `@if (descriptor; as node) {
|
|
68
|
+
<symbiote-descriptor-outlet [node]="node" />
|
|
69
|
+
}`,
|
|
70
|
+
})];
|
|
71
|
+
let _classDescriptor;
|
|
72
|
+
let _classExtraInitializers = [];
|
|
73
|
+
let _classThis;
|
|
74
|
+
let _classSuper = NativeViewBase;
|
|
75
|
+
var VideoView = class extends _classSuper {
|
|
76
|
+
static { _classThis = this; }
|
|
77
|
+
static {
|
|
78
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
79
|
+
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
80
|
+
VideoView = _classThis = _classDescriptor.value;
|
|
81
|
+
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
82
|
+
__runInitializers(_classThis, _classExtraInitializers);
|
|
83
|
+
}
|
|
84
|
+
view = createVideoView(() => this.hostNode());
|
|
85
|
+
// The functions of the view are members of the component, a `@ViewChild` reaches them
|
|
86
|
+
enterFullscreen = this.view.handle.enterFullscreen;
|
|
87
|
+
exitFullscreen = this.view.handle.exitFullscreen;
|
|
88
|
+
startPictureInPicture = this.view.handle.startPictureInPicture;
|
|
89
|
+
stopPictureInPicture = this.view.handle.stopPictureInPicture;
|
|
90
|
+
propNames = VIDEO_VIEW_INPUTS;
|
|
91
|
+
renderView(props) {
|
|
92
|
+
return this.view.render(props);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
return VideoView = _classThis;
|
|
96
|
+
})();
|
|
97
|
+
export { VideoView };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const VIDEO_MODULE_NAME = "ExpoVideo";
|
|
2
|
+
/** Upstream's label for its `UnavailabilityError` */
|
|
3
|
+
export declare const VIDEO_PACKAGE_NAME = "expo-video";
|
|
4
|
+
export declare const VIDEO_VIEW_NAMES: {
|
|
5
|
+
/** The one iOS view */
|
|
6
|
+
readonly video: "VideoView";
|
|
7
|
+
/** The Android default, renders into a `SurfaceView` */
|
|
8
|
+
readonly surface: "SurfaceVideoView";
|
|
9
|
+
/** Android, for overlapping views */
|
|
10
|
+
readonly texture: "TextureVideoView";
|
|
11
|
+
readonly airPlayButton: "VideoAirPlayButtonView";
|
|
12
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Apart from `native-module`, which resolves the module on import: the views need the name too
|
|
2
|
+
export const VIDEO_MODULE_NAME = 'ExpoVideo';
|
|
3
|
+
/** Upstream's label for its `UnavailabilityError` */
|
|
4
|
+
export const VIDEO_PACKAGE_NAME = 'expo-video';
|
|
5
|
+
export const VIDEO_VIEW_NAMES = {
|
|
6
|
+
/** The one iOS view */
|
|
7
|
+
video: 'VideoView',
|
|
8
|
+
/** The Android default, renders into a `SurfaceView` */
|
|
9
|
+
surface: 'SurfaceVideoView',
|
|
10
|
+
/** Android, for overlapping views */
|
|
11
|
+
texture: 'TextureVideoView',
|
|
12
|
+
airPlayButton: 'VideoAirPlayButtonView',
|
|
13
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { VIDEO_MODULE_NAME } from './constants';
|
|
2
|
+
export type { IVideoAudioMixingMode, IVideoAudioTrack, IVideoBufferOptions, IVideoContentType, IVideoDrmOptions, IVideoDrmType, IVideoMetadata, IVideoPlayerBuilderOptions, IVideoPlayerError, IVideoPlayerEvents, IVideoPlayerStatus, IVideoRange, IVideoScrubbingModeOptions, IVideoSeekTolerance, IVideoSize, IVideoSource, IVideoSourceObject, IVideoSubtitleTrack, IVideoThumbnailOptions, IVideoTrack, VideoPlayer, } from './player-types';
|
|
3
|
+
export { createVideoPlayer } from './video-player';
|
|
4
|
+
export { createVideoPlayerController, type IVideoPlayerController, } from './video-player-controller';
|
|
5
|
+
export { clearVideoCacheAsync, getCurrentVideoCacheSize, isPictureInPictureSupported, setVideoCacheSizeAsync, } from './video-module';
|
|
6
|
+
export { parseSource } from './video-source';
|
|
7
|
+
export { VideoThumbnail } from './video-thumbnail';
|
|
8
|
+
export { createVideoView, type IVideoView, type IVideoViewHandle, } from './video-view';
|
|
9
|
+
export { renderVideoAirPlayButton, videoAirPlayButtonViewName, } from './video-airplay-button';
|
|
10
|
+
export type { IVideoAirPlayButtonProps, IVideoButtonOptions, IVideoContentFit, IVideoFullscreenOptions, IVideoFullscreenOrientation, IVideoKeepFullscreenOnPiPStop, IVideoSurfaceType, IVideoViewProps, } from './view-types';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { VIDEO_MODULE_NAME } from './constants.js';
|
|
2
|
+
export { createVideoPlayer } from './video-player.js';
|
|
3
|
+
export { createVideoPlayerController, } from './video-player-controller.js';
|
|
4
|
+
export { clearVideoCacheAsync, getCurrentVideoCacheSize, isPictureInPictureSupported, setVideoCacheSizeAsync, } from './video-module.js';
|
|
5
|
+
export { parseSource } from './video-source.js';
|
|
6
|
+
export { VideoThumbnail } from './video-thumbnail.js';
|
|
7
|
+
export { createVideoView, } from './video-view.js';
|
|
8
|
+
export { renderVideoAirPlayButton, videoAirPlayButtonViewName, } from './video-airplay-button.js';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { VideoPlayer, VideoThumbnail } from './player-types';
|
|
2
|
+
export type INativeVideoModule = {
|
|
3
|
+
VideoPlayer: typeof VideoPlayer;
|
|
4
|
+
VideoThumbnail: typeof VideoThumbnail;
|
|
5
|
+
isPictureInPictureSupported(): boolean;
|
|
6
|
+
setVideoCacheSizeAsync(sizeBytes: number): Promise<void>;
|
|
7
|
+
clearVideoCacheAsync(): Promise<void>;
|
|
8
|
+
getCurrentVideoCacheSize(): number;
|
|
9
|
+
};
|
|
10
|
+
export declare const expoVideo: INativeVideoModule;
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import type { SharedObject, SharedRef } from 'expo-modules-core';
|
|
2
|
+
export type IVideoPlayerStatus = 'idle' | 'loading' | 'readyToPlay' | 'error';
|
|
3
|
+
export type IVideoContentType = 'auto' | 'progressive' | 'hls' | 'dash' | 'smoothStreaming';
|
|
4
|
+
export type IVideoDrmType = 'clearkey' | 'fairplay' | 'playready' | 'widevine';
|
|
5
|
+
export type IVideoDrmOptions = {
|
|
6
|
+
type: IVideoDrmType;
|
|
7
|
+
licenseServer: string;
|
|
8
|
+
headers?: Record<string, string>;
|
|
9
|
+
/** Android only */
|
|
10
|
+
multiKey?: boolean;
|
|
11
|
+
/** iOS only */
|
|
12
|
+
contentId?: string;
|
|
13
|
+
/** iOS only */
|
|
14
|
+
certificateUrl?: string;
|
|
15
|
+
/** iOS only */
|
|
16
|
+
base64CertificateData?: string;
|
|
17
|
+
};
|
|
18
|
+
export type IVideoMetadata = {
|
|
19
|
+
title?: string;
|
|
20
|
+
artist?: string;
|
|
21
|
+
artwork?: string;
|
|
22
|
+
};
|
|
23
|
+
export type IVideoSourceObject = {
|
|
24
|
+
uri?: string;
|
|
25
|
+
/** A `require`d asset, `uri` wins when both are set */
|
|
26
|
+
assetId?: number;
|
|
27
|
+
drm?: IVideoDrmOptions;
|
|
28
|
+
metadata?: IVideoMetadata;
|
|
29
|
+
headers?: Record<string, string>;
|
|
30
|
+
/** Caches the video on disk, off by default */
|
|
31
|
+
useCaching?: boolean;
|
|
32
|
+
contentType?: IVideoContentType;
|
|
33
|
+
};
|
|
34
|
+
/** A URI, a `require`d asset or an object, `null` unloads the player */
|
|
35
|
+
export type IVideoSource = string | number | null | IVideoSourceObject;
|
|
36
|
+
export type IVideoPlayerError = {
|
|
37
|
+
message: string;
|
|
38
|
+
};
|
|
39
|
+
export type IVideoAudioMixingMode = 'mixWithOthers' | 'duckOthers' | 'auto' | 'doNotMix';
|
|
40
|
+
export type IVideoBufferOptions = {
|
|
41
|
+
/** iOS only, in seconds */
|
|
42
|
+
readonly preferredForwardBufferDuration?: number;
|
|
43
|
+
/** iOS only */
|
|
44
|
+
readonly waitsToMinimizeStalling?: boolean;
|
|
45
|
+
/** Android only, in seconds */
|
|
46
|
+
readonly minBufferForPlayback?: number;
|
|
47
|
+
/** Android only, in bytes */
|
|
48
|
+
readonly maxBufferBytes?: number | null;
|
|
49
|
+
/** Android only */
|
|
50
|
+
readonly prioritizeTimeOverSizeThreshold?: boolean;
|
|
51
|
+
};
|
|
52
|
+
export type IVideoTextTrack = {
|
|
53
|
+
id?: string;
|
|
54
|
+
language: string;
|
|
55
|
+
label: string;
|
|
56
|
+
name?: string;
|
|
57
|
+
isDefault?: boolean;
|
|
58
|
+
autoSelect?: boolean;
|
|
59
|
+
};
|
|
60
|
+
export type IVideoSubtitleTrack = IVideoTextTrack;
|
|
61
|
+
export type IVideoAudioTrack = IVideoTextTrack;
|
|
62
|
+
export type IVideoSize = {
|
|
63
|
+
width: number;
|
|
64
|
+
height: number;
|
|
65
|
+
};
|
|
66
|
+
export type IVideoRange = 'sdr' | 'hlg' | 'pq';
|
|
67
|
+
export type IVideoTrack = {
|
|
68
|
+
id: string;
|
|
69
|
+
url: string | null;
|
|
70
|
+
size: IVideoSize;
|
|
71
|
+
mimeType: string | null;
|
|
72
|
+
isSupported: boolean;
|
|
73
|
+
bitrate: number | null;
|
|
74
|
+
averageBitrate: number | null;
|
|
75
|
+
peakBitrate: number | null;
|
|
76
|
+
frameRate: number | null;
|
|
77
|
+
videoRange: IVideoRange;
|
|
78
|
+
};
|
|
79
|
+
export type IVideoSeekTolerance = {
|
|
80
|
+
/** iOS only, in seconds */
|
|
81
|
+
toleranceBefore?: number;
|
|
82
|
+
/** iOS only, in seconds */
|
|
83
|
+
toleranceAfter?: number;
|
|
84
|
+
};
|
|
85
|
+
export type IVideoScrubbingModeOptions = {
|
|
86
|
+
/** Android only */
|
|
87
|
+
scrubbingModeEnabled?: boolean;
|
|
88
|
+
increaseCodecOperatingRate?: boolean;
|
|
89
|
+
enableDynamicScheduling?: boolean;
|
|
90
|
+
useDecodeOnlyFlag?: boolean;
|
|
91
|
+
allowSkippingMediaCodecFlush?: boolean;
|
|
92
|
+
};
|
|
93
|
+
/** Options of the Android player builder, applied before the native constructor runs */
|
|
94
|
+
export type IVideoPlayerBuilderOptions = {
|
|
95
|
+
/** In seconds */
|
|
96
|
+
seekBackwardIncrement?: number;
|
|
97
|
+
/** In seconds */
|
|
98
|
+
seekForwardIncrement?: number;
|
|
99
|
+
};
|
|
100
|
+
export type IVideoThumbnailOptions = {
|
|
101
|
+
maxWidth?: number;
|
|
102
|
+
maxHeight?: number;
|
|
103
|
+
};
|
|
104
|
+
export type IVideoPlayerEvents = {
|
|
105
|
+
statusChange(payload: {
|
|
106
|
+
status: IVideoPlayerStatus;
|
|
107
|
+
oldStatus?: IVideoPlayerStatus;
|
|
108
|
+
error?: IVideoPlayerError;
|
|
109
|
+
}): void;
|
|
110
|
+
playingChange(payload: {
|
|
111
|
+
isPlaying: boolean;
|
|
112
|
+
oldIsPlaying?: boolean;
|
|
113
|
+
}): void;
|
|
114
|
+
playbackRateChange(payload: {
|
|
115
|
+
playbackRate: number;
|
|
116
|
+
oldPlaybackRate?: number;
|
|
117
|
+
}): void;
|
|
118
|
+
volumeChange(payload: {
|
|
119
|
+
volume: number;
|
|
120
|
+
oldVolume?: number;
|
|
121
|
+
}): void;
|
|
122
|
+
mutedChange(payload: {
|
|
123
|
+
muted: boolean;
|
|
124
|
+
oldMuted?: boolean;
|
|
125
|
+
}): void;
|
|
126
|
+
playToEnd(): void;
|
|
127
|
+
timeUpdate(payload: {
|
|
128
|
+
currentTime: number;
|
|
129
|
+
currentLiveTimestamp: number | null;
|
|
130
|
+
currentOffsetFromLive: number | null;
|
|
131
|
+
bufferedPosition: number;
|
|
132
|
+
}): void;
|
|
133
|
+
sourceChange(payload: {
|
|
134
|
+
source: IVideoSource;
|
|
135
|
+
oldSource?: IVideoSource;
|
|
136
|
+
}): void;
|
|
137
|
+
availableSubtitleTracksChange(payload: {
|
|
138
|
+
availableSubtitleTracks: IVideoSubtitleTrack[];
|
|
139
|
+
oldAvailableSubtitleTracks?: IVideoSubtitleTrack[];
|
|
140
|
+
}): void;
|
|
141
|
+
subtitleTrackChange(payload: {
|
|
142
|
+
subtitleTrack: IVideoSubtitleTrack | null;
|
|
143
|
+
oldSubtitleTrack?: IVideoSubtitleTrack | null;
|
|
144
|
+
}): void;
|
|
145
|
+
availableAudioTracksChange(payload: {
|
|
146
|
+
availableAudioTracks: IVideoAudioTrack[];
|
|
147
|
+
oldAvailableAudioTracks?: IVideoAudioTrack[];
|
|
148
|
+
}): void;
|
|
149
|
+
audioTrackChange(payload: {
|
|
150
|
+
audioTrack: IVideoAudioTrack | null;
|
|
151
|
+
oldAudioTrack?: IVideoAudioTrack | null;
|
|
152
|
+
}): void;
|
|
153
|
+
videoTrackChange(payload: {
|
|
154
|
+
videoTrack: IVideoTrack | null;
|
|
155
|
+
oldVideoTrack?: IVideoTrack | null;
|
|
156
|
+
}): void;
|
|
157
|
+
sourceLoad(payload: {
|
|
158
|
+
videoSource: IVideoSource | null;
|
|
159
|
+
duration: number;
|
|
160
|
+
availableVideoTracks: IVideoTrack[];
|
|
161
|
+
availableSubtitleTracks: IVideoSubtitleTrack[];
|
|
162
|
+
availableAudioTracks: IVideoAudioTrack[];
|
|
163
|
+
}): void;
|
|
164
|
+
isExternalPlaybackActiveChange(payload: {
|
|
165
|
+
isExternalPlaybackActive: boolean;
|
|
166
|
+
oldIsExternalPlaybackActive?: boolean;
|
|
167
|
+
}): void;
|
|
168
|
+
};
|
|
169
|
+
/** The native thumbnail, `expo-image` takes it as an image source */
|
|
170
|
+
export declare class VideoThumbnail extends SharedRef<'image'> {
|
|
171
|
+
width: number;
|
|
172
|
+
height: number;
|
|
173
|
+
/** In seconds */
|
|
174
|
+
requestedTime: number;
|
|
175
|
+
/** In seconds, iOS only */
|
|
176
|
+
actualTime: number;
|
|
177
|
+
}
|
|
178
|
+
export declare class VideoPlayer extends SharedObject<IVideoPlayerEvents> {
|
|
179
|
+
readonly playing: boolean;
|
|
180
|
+
loop: boolean;
|
|
181
|
+
/** iOS only */
|
|
182
|
+
allowsExternalPlayback: boolean;
|
|
183
|
+
audioMixingMode: IVideoAudioMixingMode;
|
|
184
|
+
muted: boolean;
|
|
185
|
+
/** In seconds, assigning seeks */
|
|
186
|
+
currentTime: number;
|
|
187
|
+
readonly currentLiveTimestamp: number | null;
|
|
188
|
+
readonly currentOffsetFromLive: number | null;
|
|
189
|
+
targetOffsetFromLive: number;
|
|
190
|
+
readonly duration: number;
|
|
191
|
+
volume: number;
|
|
192
|
+
preservesPitch: boolean;
|
|
193
|
+
/** In seconds, `0` turns the `timeUpdate` event off */
|
|
194
|
+
timeUpdateEventInterval: number;
|
|
195
|
+
playbackRate: number;
|
|
196
|
+
keepScreenOnWhilePlaying: boolean;
|
|
197
|
+
readonly isLive: boolean;
|
|
198
|
+
readonly status: IVideoPlayerStatus;
|
|
199
|
+
showNowPlayingNotification: boolean;
|
|
200
|
+
staysActiveInBackground: boolean;
|
|
201
|
+
readonly bufferedPosition: number;
|
|
202
|
+
bufferOptions: IVideoBufferOptions;
|
|
203
|
+
subtitleTrack: IVideoSubtitleTrack | null;
|
|
204
|
+
audioTrack: IVideoAudioTrack | null;
|
|
205
|
+
readonly availableAudioTracks: IVideoAudioTrack[];
|
|
206
|
+
readonly availableSubtitleTracks: IVideoSubtitleTrack[];
|
|
207
|
+
readonly videoTrack: IVideoTrack | null;
|
|
208
|
+
readonly availableVideoTracks: IVideoTrack[];
|
|
209
|
+
readonly isExternalPlaybackActive: boolean;
|
|
210
|
+
seekTolerance: IVideoSeekTolerance;
|
|
211
|
+
scrubbingModeOptions: IVideoScrubbingModeOptions;
|
|
212
|
+
constructor(source: IVideoSource, useSynchronousReplace?: boolean, playerBuilderOptions?: IVideoPlayerBuilderOptions);
|
|
213
|
+
play(): void;
|
|
214
|
+
pause(): void;
|
|
215
|
+
/** On iOS it loads the asset on the main thread, prefer `replaceAsync` */
|
|
216
|
+
replace(source: IVideoSource, disableWarning?: boolean): void;
|
|
217
|
+
replaceAsync(source: IVideoSource): Promise<void>;
|
|
218
|
+
seekBy(seconds: number): void;
|
|
219
|
+
replay(): void;
|
|
220
|
+
generateThumbnailsAsync(times: number | number[], options?: IVideoThumbnailOptions): Promise<VideoThumbnail[]>;
|
|
221
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { IDescriptor } from '@symbiote-native/components';
|
|
2
|
+
export declare const videoAirPlayButtonViewName: () => string;
|
|
3
|
+
/** A plain view with the minimum size where the system route picker does not exist */
|
|
4
|
+
export declare function renderVideoAirPlayButton(props: object): IDescriptor;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Platform, requireNativeViewManager } from 'expo-modules-core';
|
|
2
|
+
import { el } from '@symbiote-native/components';
|
|
3
|
+
import { defineExpoNativeView } from '@symbiote-native/engine';
|
|
4
|
+
import { VIDEO_MODULE_NAME, VIDEO_VIEW_NAMES } from './constants.js';
|
|
5
|
+
// Регистрация идёт при рендере: побочный эффект барреля теряется в release
|
|
6
|
+
const airPlayButton = defineExpoNativeView(requireNativeViewManager, VIDEO_MODULE_NAME, VIDEO_VIEW_NAMES.airPlayButton);
|
|
7
|
+
export const videoAirPlayButtonViewName = airPlayButton.name;
|
|
8
|
+
const MIN_SIZE = { minWidth: 30, minHeight: 30 };
|
|
9
|
+
// What only the native route picker takes, the plain fallback view must not get it
|
|
10
|
+
const AIRPLAY_ONLY_PROPS = new Set([
|
|
11
|
+
'tint',
|
|
12
|
+
'activeTint',
|
|
13
|
+
'prioritizeVideoDevices',
|
|
14
|
+
'onBeginPresentingRoutes',
|
|
15
|
+
'onEndPresentingRoutes',
|
|
16
|
+
]);
|
|
17
|
+
function isAirPlayAvailable() {
|
|
18
|
+
return (Platform.select({ ios: true, default: false }) &&
|
|
19
|
+
airPlayButton.ensureRegistered());
|
|
20
|
+
}
|
|
21
|
+
/** A plain view with the minimum size where the system route picker does not exist */
|
|
22
|
+
export function renderVideoAirPlayButton(props) {
|
|
23
|
+
const style = [MIN_SIZE, Reflect.get(props, 'style')];
|
|
24
|
+
if (isAirPlayAvailable()) {
|
|
25
|
+
return el(airPlayButton.name(), { ...props, style });
|
|
26
|
+
}
|
|
27
|
+
const viewProps = Object.entries(props).filter(([key]) => !AIRPLAY_ONLY_PROPS.has(key));
|
|
28
|
+
return el('view', { ...Object.fromEntries(viewProps), style });
|
|
29
|
+
}
|