@symbiote-native/video 0.0.0-stage → 0.1.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.
Files changed (141) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +240 -2
  3. package/build/angular/index.d.ts +4 -0
  4. package/build/angular/index.js +5 -0
  5. package/build/angular/inject-video-player.d.ts +4 -0
  6. package/build/angular/inject-video-player.js +6 -0
  7. package/build/angular/video-airplay-button/index.d.ts +12 -0
  8. package/build/angular/video-airplay-button/index.js +78 -0
  9. package/build/angular/video-view/index.d.ts +17 -0
  10. package/build/angular/video-view/index.js +97 -0
  11. package/build/core/constants.d.ts +12 -0
  12. package/build/core/constants.js +13 -0
  13. package/build/core/index.d.ts +10 -0
  14. package/build/core/index.js +8 -0
  15. package/build/core/native-module.d.ts +10 -0
  16. package/build/core/native-module.js +3 -0
  17. package/build/core/player-types.d.ts +221 -0
  18. package/build/core/player-types.js +1 -0
  19. package/build/core/video-airplay-button.d.ts +4 -0
  20. package/build/core/video-airplay-button.js +29 -0
  21. package/build/core/video-module.d.ts +12 -0
  22. package/build/core/video-module.js +21 -0
  23. package/build/core/video-player-controller.d.ts +15 -0
  24. package/build/core/video-player-controller.js +21 -0
  25. package/build/core/video-player.d.ts +7 -0
  26. package/build/core/video-player.js +22 -0
  27. package/build/core/video-source.d.ts +3 -0
  28. package/build/core/video-source.js +20 -0
  29. package/build/core/video-thumbnail.d.ts +4 -0
  30. package/build/core/video-thumbnail.js +3 -0
  31. package/build/core/video-view.d.ts +18 -0
  32. package/build/core/video-view.js +63 -0
  33. package/build/core/view-types.d.ts +90 -0
  34. package/build/core/view-types.js +1 -0
  35. package/build/react/index.d.ts +4 -0
  36. package/build/react/index.js +4 -0
  37. package/build/react/use-video-player.d.ts +7 -0
  38. package/build/react/use-video-player.js +11 -0
  39. package/build/react/video-view.d.ts +13 -0
  40. package/build/react/video-view.js +10 -0
  41. package/build/solid/index.d.ts +3 -0
  42. package/build/solid/index.js +4 -0
  43. package/build/solid/use-video-player.d.ts +8 -0
  44. package/build/solid/use-video-player.js +11 -0
  45. package/build/solid/video-view.d.ts +33 -0
  46. package/build/solid/video-view.js +6 -0
  47. package/build/svelte/index.d.ts +5 -0
  48. package/build/svelte/index.js +5 -0
  49. package/build/svelte/use-video-player.svelte.d.ts +10 -0
  50. package/build/svelte/use-video-player.svelte.js +15 -0
  51. package/build/svelte/video-airplay-button.svelte +12 -0
  52. package/build/svelte/video-airplay-button.svelte.d.ts +4 -0
  53. package/build/svelte/video-view.svelte +25 -0
  54. package/build/svelte/video-view.svelte.d.ts +9 -0
  55. package/build/vue/index.d.ts +3 -0
  56. package/build/vue/index.js +4 -0
  57. package/build/vue/use-video-player.d.ts +8 -0
  58. package/build/vue/use-video-player.js +16 -0
  59. package/build/vue/video-view.d.ts +5 -0
  60. package/build/vue/video-view.js +14 -0
  61. package/build-ngc/angular/index.d.ts +4 -0
  62. package/build-ngc/angular/index.js +6 -0
  63. package/build-ngc/angular/index.js.map +1 -0
  64. package/build-ngc/angular/inject-video-player.d.ts +4 -0
  65. package/build-ngc/angular/inject-video-player.js +7 -0
  66. package/build-ngc/angular/inject-video-player.js.map +1 -0
  67. package/build-ngc/angular/video-airplay-button/index.d.ts +15 -0
  68. package/build-ngc/angular/video-airplay-button/index.js +37 -0
  69. package/build-ngc/angular/video-airplay-button/index.js.map +1 -0
  70. package/build-ngc/angular/video-view/index.d.ts +20 -0
  71. package/build-ngc/angular/video-view/index.js +56 -0
  72. package/build-ngc/angular/video-view/index.js.map +1 -0
  73. package/build-ngc/core/constants.d.ts +12 -0
  74. package/build-ngc/core/constants.js +14 -0
  75. package/build-ngc/core/constants.js.map +1 -0
  76. package/build-ngc/core/index.d.ts +10 -0
  77. package/build-ngc/core/index.js +9 -0
  78. package/build-ngc/core/index.js.map +1 -0
  79. package/build-ngc/core/native-module.d.ts +10 -0
  80. package/build-ngc/core/native-module.js +4 -0
  81. package/build-ngc/core/native-module.js.map +1 -0
  82. package/build-ngc/core/player-types.d.ts +221 -0
  83. package/build-ngc/core/player-types.js +2 -0
  84. package/build-ngc/core/player-types.js.map +1 -0
  85. package/build-ngc/core/video-airplay-button.d.ts +4 -0
  86. package/build-ngc/core/video-airplay-button.js +30 -0
  87. package/build-ngc/core/video-airplay-button.js.map +1 -0
  88. package/build-ngc/core/video-module.d.ts +12 -0
  89. package/build-ngc/core/video-module.js +22 -0
  90. package/build-ngc/core/video-module.js.map +1 -0
  91. package/build-ngc/core/video-player-controller.d.ts +15 -0
  92. package/build-ngc/core/video-player-controller.js +22 -0
  93. package/build-ngc/core/video-player-controller.js.map +1 -0
  94. package/build-ngc/core/video-player.d.ts +7 -0
  95. package/build-ngc/core/video-player.js +23 -0
  96. package/build-ngc/core/video-player.js.map +1 -0
  97. package/build-ngc/core/video-source.d.ts +3 -0
  98. package/build-ngc/core/video-source.js +21 -0
  99. package/build-ngc/core/video-source.js.map +1 -0
  100. package/build-ngc/core/video-thumbnail.d.ts +4 -0
  101. package/build-ngc/core/video-thumbnail.js +4 -0
  102. package/build-ngc/core/video-thumbnail.js.map +1 -0
  103. package/build-ngc/core/video-view.d.ts +18 -0
  104. package/build-ngc/core/video-view.js +64 -0
  105. package/build-ngc/core/video-view.js.map +1 -0
  106. package/build-ngc/core/view-types.d.ts +90 -0
  107. package/build-ngc/core/view-types.js +2 -0
  108. package/build-ngc/core/view-types.js.map +1 -0
  109. package/native-link.json +33 -0
  110. package/package.json +144 -3
  111. package/src/angular/index.ts +6 -0
  112. package/src/angular/inject-video-player.ts +20 -0
  113. package/src/angular/video-airplay-button/index.ts +38 -0
  114. package/src/angular/video-view/index.ts +59 -0
  115. package/src/core/constants.ts +16 -0
  116. package/src/core/index.ts +56 -0
  117. package/src/core/native-module.ts +15 -0
  118. package/src/core/player-types.ts +241 -0
  119. package/src/core/video-airplay-button.ts +45 -0
  120. package/src/core/video-module.ts +25 -0
  121. package/src/core/video-player-controller.ts +53 -0
  122. package/src/core/video-player.ts +38 -0
  123. package/src/core/video-source.ts +21 -0
  124. package/src/core/video-thumbnail.ts +7 -0
  125. package/src/core/video-view.ts +119 -0
  126. package/src/core/view-types.ts +114 -0
  127. package/src/react/index.ts +6 -0
  128. package/src/react/use-video-player.ts +22 -0
  129. package/src/react/video-view.ts +35 -0
  130. package/src/solid/index.ts +5 -0
  131. package/src/solid/use-video-player.ts +24 -0
  132. package/src/solid/video-view.ts +22 -0
  133. package/src/svelte/index.ts +7 -0
  134. package/src/svelte/svelte-ambient.d.ts +3 -0
  135. package/src/svelte/svelte-compile.test-helper.ts +6 -0
  136. package/src/svelte/use-video-player.svelte.ts +31 -0
  137. package/src/svelte/video-airplay-button.svelte +12 -0
  138. package/src/svelte/video-view.svelte +25 -0
  139. package/src/vue/index.ts +5 -0
  140. package/src/vue/use-video-player.ts +33 -0
  141. package/src/vue/video-view.ts +24 -0
@@ -0,0 +1,25 @@
1
+ import { expoVideo } from './native-module';
2
+
3
+ /** Whether the device supports Picture in Picture */
4
+ export function isPictureInPictureSupported(): boolean {
5
+ return expoVideo.isPictureInPictureSupported();
6
+ }
7
+
8
+ /** Clears the video cache, only while no `VideoPlayer` exists */
9
+ export function clearVideoCacheAsync(): Promise<void> {
10
+ return expoVideo.clearVideoCacheAsync();
11
+ }
12
+
13
+ /**
14
+ * Sets the cache size in bytes, 1GB by default, and the value persists
15
+ *
16
+ * Only while no `VideoPlayer` exists, and the real size may be slightly larger
17
+ */
18
+ export function setVideoCacheSizeAsync(sizeBytes: number): Promise<void> {
19
+ return expoVideo.setVideoCacheSizeAsync(sizeBytes);
20
+ }
21
+
22
+ /** The space the video cache occupies, in bytes */
23
+ export function getCurrentVideoCacheSize(): number {
24
+ return expoVideo.getCurrentVideoCacheSize();
25
+ }
@@ -0,0 +1,53 @@
1
+ import { createJsonKeyedResourceController } from '@symbiote-native/engine';
2
+ import type { IResourceController } from '@symbiote-native/engine';
3
+ import type {
4
+ IVideoPlayerBuilderOptions,
5
+ IVideoSource,
6
+ VideoPlayer,
7
+ } from './player-types';
8
+ import { createVideoPlayer } from './video-player';
9
+ import { parseSource } from './video-source';
10
+
11
+ type IVideoPlayerKey = {
12
+ source: IVideoSource;
13
+ options?: IVideoPlayerBuilderOptions;
14
+ };
15
+
16
+ export type IVideoPlayerController = {
17
+ /** `setup` runs only on a player this call creates */
18
+ resolve: (
19
+ source: IVideoSource,
20
+ options?: IVideoPlayerBuilderOptions,
21
+ setup?: (player: VideoPlayer) => void,
22
+ ) => VideoPlayer;
23
+ flushDispose: IResourceController<
24
+ IVideoPlayerKey,
25
+ VideoPlayer
26
+ >['flushDispose'];
27
+ dispose: IResourceController<IVideoPlayerKey, VideoPlayer>['dispose'];
28
+ };
29
+
30
+ /** Twin of `useVideoPlayer`'s releasing shared object, one per mounted owner */
31
+ export function createVideoPlayerController(): IVideoPlayerController {
32
+ // The latest setup, it is not part of the key and must not recreate the player
33
+ let latestSetup: ((player: VideoPlayer) => void) | undefined;
34
+ const controller = createJsonKeyedResourceController<
35
+ IVideoPlayerKey,
36
+ VideoPlayer
37
+ >(
38
+ key => {
39
+ const player = createVideoPlayer(key.source, key.options);
40
+ latestSetup?.(player);
41
+ return player;
42
+ },
43
+ player => player.release(),
44
+ );
45
+ return {
46
+ resolve: (source, options, setup) => {
47
+ latestSetup = setup;
48
+ return controller.resolve({ source: parseSource(source), options });
49
+ },
50
+ flushDispose: controller.flushDispose,
51
+ dispose: controller.dispose,
52
+ };
53
+ }
@@ -0,0 +1,38 @@
1
+ import { expoVideo } from './native-module';
2
+ import type {
3
+ IVideoPlayerBuilderOptions,
4
+ IVideoSource,
5
+ VideoPlayer,
6
+ } from './player-types';
7
+ import { parseSource } from './video-source';
8
+
9
+ const SYNC_REPLACE_WARNING =
10
+ 'On iOS `VideoPlayer.replace` loads the asset data synchronously on the main thread, which can lead to UI freezes and will be deprecated in a future release. Switch to `replaceAsync` for better user experience.';
11
+
12
+ // Upstream patches these two methods onto the native prototype at load, a subclass does the same
13
+ class SourceParsingVideoPlayer extends expoVideo.VideoPlayer {
14
+ override replace(source: IVideoSource, disableWarning = false): void {
15
+ if (!disableWarning) console.warn(SYNC_REPLACE_WARNING);
16
+ super.replace(parseSource(source));
17
+ }
18
+
19
+ override replaceAsync(source: IVideoSource): Promise<void> {
20
+ return super.replaceAsync(parseSource(source));
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Creates a player that does not release automatically, call `release()` when done
26
+ *
27
+ * The adapter hooks release it on unmount
28
+ */
29
+ export function createVideoPlayer(
30
+ source: IVideoSource,
31
+ playerBuilderOptions?: IVideoPlayerBuilderOptions,
32
+ ): VideoPlayer {
33
+ return new SourceParsingVideoPlayer(
34
+ parseSource(source),
35
+ false,
36
+ playerBuilderOptions,
37
+ );
38
+ }
@@ -0,0 +1,21 @@
1
+ import { resolveAssetSource } from '@symbiote-native/engine';
2
+ import type { IVideoSource } from './player-types';
3
+
4
+ function uriOfAsset(assetId: number): string {
5
+ const resolved = resolveAssetSource(assetId);
6
+ const uri: unknown = Reflect.get(Object(resolved), 'uri');
7
+ if (typeof uri !== 'string') {
8
+ throw new Error(`The asset ${assetId} could not be resolved`);
9
+ }
10
+ return uri;
11
+ }
12
+
13
+ /** What the native player takes: a `require`d asset and a string both become a uri */
14
+ export function parseSource(source: IVideoSource): IVideoSource {
15
+ if (typeof source === 'number') return { uri: uriOfAsset(source) };
16
+ if (typeof source === 'string') return { uri: source };
17
+ if (typeof source?.assetId === 'number' && !source.uri) {
18
+ return { ...source, uri: uriOfAsset(source.assetId) };
19
+ }
20
+ return source;
21
+ }
@@ -0,0 +1,7 @@
1
+ import { expoVideo } from './native-module';
2
+ import type { VideoThumbnail as IVideoThumbnailInstance } from './player-types';
3
+
4
+ /** The native class of a thumbnail, `generateThumbnailsAsync` resolves to instances of it */
5
+ export const VideoThumbnail = expoVideo.VideoThumbnail;
6
+
7
+ export type VideoThumbnail = IVideoThumbnailInstance;
@@ -0,0 +1,119 @@
1
+ // Ядро `VideoView`: нативный view плеера, функции вызываются через `ViewPrototypes`
2
+ import {
3
+ Platform,
4
+ requireNativeModule,
5
+ requireNativeViewManager,
6
+ } from 'expo-modules-core';
7
+ import { el } from '@symbiote-native/components';
8
+ import type { IDescriptor } from '@symbiote-native/components';
9
+ import {
10
+ defineExpoNativeViews,
11
+ defineExpoViewMethods,
12
+ getNativeTag,
13
+ isDevBuild,
14
+ } from '@symbiote-native/engine';
15
+ import type {
16
+ IExpoViewMethodCaller,
17
+ ISymbioteNode,
18
+ } from '@symbiote-native/engine';
19
+ import { VIDEO_MODULE_NAME, VIDEO_VIEW_NAMES } from './constants';
20
+ import { expoVideo } from './native-module';
21
+
22
+ type IVideoViewKey = 'video' | 'surface' | 'texture';
23
+
24
+ const VIEW_NAMES = {
25
+ video: VIDEO_VIEW_NAMES.video,
26
+ surface: VIDEO_VIEW_NAMES.surface,
27
+ texture: VIDEO_VIEW_NAMES.texture,
28
+ } as const satisfies Record<IVideoViewKey, string>;
29
+
30
+ // Регистрация идёт при рендере: побочный эффект барреля теряется в release
31
+ const views = defineExpoNativeViews(
32
+ requireNativeViewManager,
33
+ VIDEO_MODULE_NAME,
34
+ VIEW_NAMES,
35
+ );
36
+
37
+ const callers: Record<IVideoViewKey, IExpoViewMethodCaller> = {
38
+ video: defineExpoViewMethods(
39
+ requireNativeModule,
40
+ VIDEO_MODULE_NAME,
41
+ VIEW_NAMES.video,
42
+ ),
43
+ surface: defineExpoViewMethods(
44
+ requireNativeModule,
45
+ VIDEO_MODULE_NAME,
46
+ VIEW_NAMES.surface,
47
+ ),
48
+ texture: defineExpoViewMethods(
49
+ requireNativeModule,
50
+ VIDEO_MODULE_NAME,
51
+ VIEW_NAMES.texture,
52
+ ),
53
+ };
54
+
55
+ export type IVideoViewHandle = {
56
+ /** Always shows the native controls */
57
+ enterFullscreen(): Promise<void>;
58
+ /** On Android it works only from a native listener, the JS runtime sleeps in fullscreen */
59
+ exitFullscreen(): Promise<void>;
60
+ /** Needs `supportsPictureInPicture` of the app setup, one player at a time */
61
+ startPictureInPicture(): Promise<void>;
62
+ stopPictureInPicture(): Promise<void>;
63
+ };
64
+
65
+ export type IVideoView = {
66
+ handle: IVideoViewHandle;
67
+ /** `null` means the view cannot register and the caller renders nothing */
68
+ render(props: object): IDescriptor | null;
69
+ };
70
+
71
+ function viewKeyFor(surfaceType: unknown): IVideoViewKey {
72
+ return Platform.select<IVideoViewKey>({
73
+ android: surfaceType === 'textureView' ? 'texture' : 'surface',
74
+ default: 'video',
75
+ });
76
+ }
77
+
78
+ // The native view takes the id of the shared object, not the player itself
79
+ function playerIdOf(player: unknown): number | null {
80
+ if (player instanceof expoVideo.VideoPlayer) {
81
+ const id: unknown = Reflect.get(player, '__expo_shared_object_id__');
82
+ return typeof id === 'number' ? id : null;
83
+ }
84
+ return typeof player === 'number' ? player : null;
85
+ }
86
+
87
+ /** One per mounted view, `getNode` answers the host node once it exists */
88
+ export function createVideoView(
89
+ getNode: () => ISymbioteNode | null | undefined,
90
+ ): IVideoView {
91
+ let renderedKey = viewKeyFor(undefined);
92
+
93
+ async function call(method: string): Promise<void> {
94
+ const node = getNode();
95
+ if (!node || getNativeTag(node) === undefined) return;
96
+ await callers[renderedKey]<Promise<void>>(node, method, []);
97
+ }
98
+
99
+ return {
100
+ handle: {
101
+ enterFullscreen: () => call('enterFullscreen'),
102
+ exitFullscreen: () => call('exitFullscreen'),
103
+ startPictureInPicture: () => call('startPictureInPicture'),
104
+ stopPictureInPicture: () => call('stopPictureInPicture'),
105
+ },
106
+ render: props => {
107
+ renderedKey = viewKeyFor(Reflect.get(props, 'surfaceType'));
108
+ const view = views[renderedKey];
109
+ if (!view.ensureRegistered()) {
110
+ if (isDevBuild()) console.warn("'VideoView' is not available.");
111
+ return null;
112
+ }
113
+ return el(view.name(), {
114
+ ...Object.fromEntries(Object.entries(props)),
115
+ player: playerIdOf(Reflect.get(props, 'player')),
116
+ });
117
+ },
118
+ };
119
+ }
@@ -0,0 +1,114 @@
1
+ import type {
2
+ IAccessibilityProps,
3
+ IAriaProps,
4
+ IResponderProps,
5
+ } from '@symbiote-native/components';
6
+ import type {
7
+ IColorValue,
8
+ IStyleProp,
9
+ ISymbioteEvent,
10
+ IViewStyle,
11
+ } from '@symbiote-native/engine';
12
+ import type { VideoPlayer } from './player-types';
13
+
14
+ /** `fill` stretches the video, `cover` crops it, `contain` letterboxes it */
15
+ export type IVideoContentFit = 'contain' | 'cover' | 'fill';
16
+
17
+ /** Android only: `textureView` for overlapping views, `surfaceView` otherwise */
18
+ export type IVideoSurfaceType = 'textureView' | 'surfaceView';
19
+
20
+ /** Android only, which buttons the native controls show */
21
+ export type IVideoButtonOptions = {
22
+ /** `false` by default */
23
+ showNext?: boolean;
24
+ /** `false` by default */
25
+ showPrevious?: boolean;
26
+ /** `true` by default */
27
+ showSeekForward?: boolean;
28
+ /** `true` by default */
29
+ showSeekBackward?: boolean;
30
+ /** `true` always, `false` never, `null` only when subtitles exist */
31
+ showSubtitles?: boolean | null;
32
+ /** `true` by default */
33
+ showSettings?: boolean;
34
+ /** `true` by default */
35
+ showPlayPause?: boolean;
36
+ /** `true` by default, fullscreen shows it anyway */
37
+ showBottomBar?: boolean;
38
+ };
39
+
40
+ export type IVideoFullscreenOrientation =
41
+ | 'default'
42
+ | 'portrait'
43
+ | 'portraitUp'
44
+ | 'portraitDown'
45
+ | 'landscape'
46
+ | 'landscapeLeft'
47
+ | 'landscapeRight';
48
+
49
+ export type IVideoKeepFullscreenOnPiPStop = 'always' | 'autoEnter' | 'never';
50
+
51
+ export type IVideoFullscreenOptions = {
52
+ /** Shows the fullscreen button, `true` by default */
53
+ enable: boolean;
54
+ orientation?: IVideoFullscreenOrientation;
55
+ /** Leaves fullscreen when the device turns away from `orientation` */
56
+ autoExitOnRotate?: boolean;
57
+ /** iOS only, `autoEnter` by default */
58
+ keepFullscreenOnPiPStop?: IVideoKeepFullscreenOnPiPStop;
59
+ };
60
+
61
+ export type IVideoViewProps = IAccessibilityProps &
62
+ IAriaProps &
63
+ IResponderProps & {
64
+ /** From `useVideoPlayer` */
65
+ player?: VideoPlayer | null;
66
+ /** Always on in fullscreen, `true` by default */
67
+ nativeControls?: boolean;
68
+ /** `contain` by default */
69
+ contentFit?: IVideoContentFit;
70
+ fullscreenOptions?: IVideoFullscreenOptions;
71
+ /** iOS only, `true` by default */
72
+ showsTimecodes?: boolean;
73
+ /** Forbids skipping, `false` by default */
74
+ requiresLinearPlayback?: boolean;
75
+ buttonOptions?: IVideoButtonOptions;
76
+ /** Android only, must not change at runtime */
77
+ surfaceType?: IVideoSurfaceType;
78
+ /** iOS only, `{ dx: 0, dy: 0 }` by default */
79
+ contentPosition?: { dx?: number; dy?: number };
80
+ onPictureInPictureStart?: () => void;
81
+ onPictureInPictureStop?: () => void;
82
+ /** Needs `supportsPictureInPicture` of the app setup */
83
+ allowsPictureInPicture?: boolean;
84
+ /** Android 12+ and iOS, `false` by default */
85
+ startsPictureInPictureAutomatically?: boolean;
86
+ /** iOS 16+, `true` by default */
87
+ allowsVideoFrameAnalysis?: boolean;
88
+ onFullscreenEnter?: () => void;
89
+ onFullscreenExit?: () => void;
90
+ /** The first frame is rendered, also when the video track changes */
91
+ onFirstFrameRender?: () => void;
92
+ /** Android only, `false` by default */
93
+ useExoShutter?: boolean;
94
+ style?: IStyleProp<IViewStyle>;
95
+ testID?: string;
96
+ nativeID?: string;
97
+ onLayout?: (event: ISymbioteEvent) => void;
98
+ };
99
+
100
+ export type IVideoAirPlayButtonProps = IAccessibilityProps &
101
+ IAriaProps & {
102
+ /** The icon colour while AirPlay is not active */
103
+ tint?: IColorValue;
104
+ /** The icon colour while AirPlay is active */
105
+ activeTint?: IColorValue;
106
+ /** Lists the video outputs first, `true` by default */
107
+ prioritizeVideoDevices?: boolean;
108
+ onBeginPresentingRoutes?: () => void;
109
+ onEndPresentingRoutes?: () => void;
110
+ style?: IStyleProp<IViewStyle>;
111
+ testID?: string;
112
+ nativeID?: string;
113
+ onLayout?: (event: ISymbioteEvent) => void;
114
+ };
@@ -0,0 +1,6 @@
1
+ // @symbiote-native/video/react: плеер, view, миниатюры, кэш и кнопка AirPlay на общем ядре
2
+
3
+ export { VideoAirPlayButton, VideoView } from './video-view';
4
+ export type { IVideoViewReactProps } from './video-view';
5
+ export { useVideoPlayer } from './use-video-player';
6
+ export * from '../core';
@@ -0,0 +1,22 @@
1
+ import { createResourceHook } from '@symbiote-native/react';
2
+ import { createVideoPlayerController } from '../core/video-player-controller';
3
+ import type {
4
+ IVideoPlayerBuilderOptions,
5
+ IVideoSource,
6
+ VideoPlayer,
7
+ } from '../core';
8
+
9
+ const usePlayerResource = createResourceHook(createVideoPlayerController);
10
+
11
+ /**
12
+ * Creates a player that is released when the component unmounts
13
+ *
14
+ * `setup` runs once on each new player, a changed source or options make a new one
15
+ */
16
+ export function useVideoPlayer(
17
+ source: IVideoSource,
18
+ setup?: (player: VideoPlayer) => void,
19
+ playerBuilderOptions?: IVideoPlayerBuilderOptions,
20
+ ): VideoPlayer {
21
+ return usePlayerResource(source, playerBuilderOptions, setup);
22
+ }
@@ -0,0 +1,35 @@
1
+ import type { ReactElement, Ref } from 'react';
2
+ import {
3
+ descriptorToReact,
4
+ useNativeViewController,
5
+ } from '@symbiote-native/react';
6
+ import { createVideoView, renderVideoAirPlayButton } from '../core';
7
+ import type {
8
+ IVideoAirPlayButtonProps,
9
+ IVideoViewHandle,
10
+ IVideoViewProps,
11
+ } from '../core';
12
+
13
+ export type IVideoViewReactProps = IVideoViewProps & {
14
+ ref?: Ref<IVideoViewHandle>;
15
+ className?: string;
16
+ };
17
+
18
+ export type IVideoAirPlayButtonReactProps = IVideoAirPlayButtonProps & {
19
+ className?: string;
20
+ };
21
+
22
+ /** React twin of `expo-video`'s `VideoView` */
23
+ export function VideoView({
24
+ ref,
25
+ ...props
26
+ }: IVideoViewReactProps): ReactElement | null {
27
+ return useNativeViewController(ref, createVideoView, props);
28
+ }
29
+
30
+ /** React twin of `expo-video`'s `VideoAirPlayButton`, a plain view off iOS */
31
+ export function VideoAirPlayButton(
32
+ props: IVideoAirPlayButtonReactProps,
33
+ ): ReactElement {
34
+ return descriptorToReact(renderVideoAirPlayButton(props));
35
+ }
@@ -0,0 +1,5 @@
1
+ // @symbiote-native/video/solid: плеер, view, миниатюры, кэш и кнопка AirPlay на общем ядре
2
+
3
+ export { VideoAirPlayButton, VideoView } from './video-view';
4
+ export { useVideoPlayer } from './use-video-player';
5
+ export * from '../core';
@@ -0,0 +1,24 @@
1
+ import type { Accessor } from 'solid-js';
2
+ import { createResourceHook } from '@symbiote-native/solid';
3
+ import { createVideoPlayerController } from '../core/video-player-controller';
4
+ import type {
5
+ IVideoPlayerBuilderOptions,
6
+ IVideoSource,
7
+ VideoPlayer,
8
+ } from '../core';
9
+
10
+ const usePlayerResource = createResourceHook(createVideoPlayerController);
11
+
12
+ /**
13
+ * Solid twin of `expo-video`'s `useVideoPlayer`, released when the owner is disposed
14
+ *
15
+ * `setup` runs once on each new player, a changed source or options make a new one
16
+ */
17
+ export function useVideoPlayer(
18
+ source: Accessor<IVideoSource>,
19
+ setup?: (player: VideoPlayer) => void,
20
+ playerBuilderOptions: Accessor<IVideoPlayerBuilderOptions | undefined> = () =>
21
+ undefined,
22
+ ): Accessor<VideoPlayer> {
23
+ return usePlayerResource(() => [source(), playerBuilderOptions(), setup]);
24
+ }
@@ -0,0 +1,22 @@
1
+ import {
2
+ defineNativeViewComponent,
3
+ defineOptionalDescriptorComponent,
4
+ } from '@symbiote-native/solid';
5
+ import { createVideoView, renderVideoAirPlayButton } from '../core';
6
+ import type {
7
+ IVideoAirPlayButtonProps,
8
+ IVideoViewHandle,
9
+ IVideoViewProps,
10
+ } from '../core';
11
+
12
+ /** Solid twin of `expo-video`'s `VideoView`, `ref` gets the handle of the view */
13
+ export const VideoView = defineNativeViewComponent<
14
+ IVideoViewHandle,
15
+ IVideoViewProps
16
+ >(createVideoView);
17
+
18
+ /** Solid twin of `expo-video`'s `VideoAirPlayButton`, a plain view off iOS */
19
+ export const VideoAirPlayButton =
20
+ defineOptionalDescriptorComponent<IVideoAirPlayButtonProps>(
21
+ renderVideoAirPlayButton,
22
+ );
@@ -0,0 +1,7 @@
1
+ // @symbiote-native/video/svelte: плеер, view, миниатюры, кэш и кнопка AirPlay на общем ядре
2
+
3
+ export { default as VideoView } from './video-view.svelte';
4
+ export { default as VideoAirPlayButton } from './video-airplay-button.svelte';
5
+ export { useVideoPlayer } from './use-video-player.svelte';
6
+ export type { IUseVideoPlayerResult } from './use-video-player.svelte';
7
+ export * from '../core';
@@ -0,0 +1,3 @@
1
+ // Pulls in svelte's ambient globals ($state/$effect/...) for this package's tsc program
2
+ // This package has no value import from 'svelte', so a triple-slash reference does it explicitly
3
+ /// <reference types="svelte" />
@@ -0,0 +1,6 @@
1
+ // Re-exports the existing harness instead of another copy
2
+ export {
3
+ createSvelteHarness,
4
+ loadComponent,
5
+ type ISvelteHarness,
6
+ } from '../../../sqlite/src/svelte/svelte-compile.test-helper';
@@ -0,0 +1,31 @@
1
+ import { createResourceHook } from '@symbiote-native/svelte/runes/create-resource-hook';
2
+ import { createVideoPlayerController } from '../core/video-player-controller';
3
+ import type {
4
+ IVideoPlayerBuilderOptions,
5
+ IVideoSource,
6
+ VideoPlayer,
7
+ } from '../core';
8
+
9
+ export type IUseVideoPlayerResult = {
10
+ readonly current: VideoPlayer;
11
+ };
12
+
13
+ const usePlayerResource = createResourceHook(createVideoPlayerController);
14
+
15
+ /**
16
+ * Svelte twin of `expo-video`'s `useVideoPlayer`, boxed getter shape of the other resource hooks
17
+ *
18
+ * `setup` runs once on each new player, a changed source or options make a new one
19
+ */
20
+ export function useVideoPlayer(
21
+ getSource: () => IVideoSource,
22
+ setup?: (player: VideoPlayer) => void,
23
+ getPlayerBuilderOptions: () => IVideoPlayerBuilderOptions | undefined = () =>
24
+ undefined,
25
+ ): IUseVideoPlayerResult {
26
+ return usePlayerResource(() => [
27
+ getSource(),
28
+ getPlayerBuilderOptions(),
29
+ setup,
30
+ ]);
31
+ }
@@ -0,0 +1,12 @@
1
+ <script lang="ts">
2
+ // The native view name is mixed-case, which a literal tag would parse as a component reference,
3
+ // so the tag is dynamic
4
+ import { hostProps } from '@symbiote-native/svelte/native-view-bridge';
5
+ import { renderVideoAirPlayButton } from '../core';
6
+ import type { IVideoAirPlayButtonProps } from '../core';
7
+
8
+ const props: IVideoAirPlayButtonProps = $props();
9
+ const descriptor = $derived(renderVideoAirPlayButton({ ...props }));
10
+ </script>
11
+
12
+ <svelte:element this={descriptor.type} {@attach hostProps(descriptor.props)} />
@@ -0,0 +1,25 @@
1
+ <script lang="ts">
2
+ // The native view name is mixed-case, which a literal tag would parse as a component reference,
3
+ // so the tag is dynamic
4
+ import { hostProps } from '@symbiote-native/svelte/native-view-bridge';
5
+ import { useNativeViewController } from '@symbiote-native/svelte/runes/use-native-view-controller';
6
+ import { createVideoView } from '../core';
7
+ import type { IVideoViewProps } from '../core';
8
+
9
+ const props: IVideoViewProps = $props();
10
+ const view = useNativeViewController(createVideoView, () => ({ ...props }));
11
+
12
+ // The functions of the view are instance members, `bind:this` reaches them
13
+ export const enterFullscreen = view.handle.enterFullscreen;
14
+ export const exitFullscreen = view.handle.exitFullscreen;
15
+ export const startPictureInPicture = view.handle.startPictureInPicture;
16
+ export const stopPictureInPicture = view.handle.stopPictureInPicture;
17
+ </script>
18
+
19
+ {#if view.descriptor}
20
+ <svelte:element
21
+ this={view.descriptor.type}
22
+ {@attach view.attachHost}
23
+ {@attach hostProps(view.descriptor.props)}
24
+ />
25
+ {/if}
@@ -0,0 +1,5 @@
1
+ // @symbiote-native/video/vue: плеер, view, миниатюры, кэш и кнопка AirPlay на общем ядре
2
+
3
+ export { VideoAirPlayButton, VideoView } from './video-view';
4
+ export { useVideoPlayer } from './use-video-player';
5
+ export * from '../core';
@@ -0,0 +1,33 @@
1
+ import {
2
+ toValue,
3
+ type ComputedRef,
4
+ type MaybeRefOrGetter,
5
+ } from '@vue/runtime-core';
6
+ import { createResourceHook } from '@symbiote-native/vue';
7
+ import { createVideoPlayerController } from '../core/video-player-controller';
8
+ import type {
9
+ IVideoPlayerBuilderOptions,
10
+ IVideoSource,
11
+ VideoPlayer,
12
+ } from '../core';
13
+
14
+ const usePlayerResource = createResourceHook(createVideoPlayerController);
15
+
16
+ /**
17
+ * Vue twin of `expo-video`'s `useVideoPlayer`, released when the owner unmounts
18
+ *
19
+ * `setup` runs once on each new player, a changed source or options make a new one
20
+ */
21
+ export function useVideoPlayer(
22
+ source: MaybeRefOrGetter<IVideoSource>,
23
+ setup?: (player: VideoPlayer) => void,
24
+ playerBuilderOptions?: MaybeRefOrGetter<
25
+ IVideoPlayerBuilderOptions | undefined
26
+ >,
27
+ ): ComputedRef<VideoPlayer> {
28
+ return usePlayerResource(() => [
29
+ toValue(source),
30
+ toValue(playerBuilderOptions),
31
+ setup,
32
+ ]);
33
+ }
@@ -0,0 +1,24 @@
1
+ import { defineComponent, type VNode } from '@vue/runtime-core';
2
+ import {
3
+ defineNativeViewComponent,
4
+ descriptorToVue,
5
+ normalizeVueAttrs,
6
+ } from '@symbiote-native/vue';
7
+ import { createVideoView, renderVideoAirPlayButton } from '../core';
8
+
9
+ /** Vue twin of `expo-video`'s `VideoView` */
10
+ export const VideoView = defineNativeViewComponent(
11
+ 'VideoView',
12
+ createVideoView,
13
+ );
14
+
15
+ /** Vue twin of `expo-video`'s `VideoAirPlayButton`, a plain view off iOS */
16
+ export const VideoAirPlayButton = defineComponent({
17
+ name: 'VideoAirPlayButton',
18
+ // Все атрибуты это пропсы нативного view, на обёртку ничего не должно просочиться
19
+ inheritAttrs: false,
20
+ setup(_props, { attrs }) {
21
+ return (): VNode =>
22
+ descriptorToVue(renderVideoAirPlayButton(normalizeVueAttrs(attrs)));
23
+ },
24
+ });