expo-video-subtitle 0.3.0 → 0.4.1

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/CHANGELOG.md CHANGED
@@ -7,6 +7,67 @@ Changes inherited from the upstream base version are not repeated here — see t
7
7
  [upstream changelog](https://github.com/expo/expo/blob/main/packages/expo-video/CHANGELOG.md)
8
8
  for the history of everything that isn't subtitle related.
9
9
 
10
+ ## 0.4.1
11
+
12
+ ### Fixed
13
+
14
+ - **iOS:** `SubtitleSource.bottomOffset` now anchors the **bottom** of the subtitle block, the same way
15
+ Android does, so one value places captions identically on both platforms.
16
+
17
+ 0.4.0 emitted a bare WebVTT `line:` percentage, which anchors the *top* of the cue box. A cue that
18
+ wrapped onto two lines therefore sat a full line lower on iOS than on Android — 5–13% of the video
19
+ height at typical caption sizes, comparable to the offset itself, and enough to drop the second line
20
+ back over the burned-in captions the offset exists to escape. The cue setting is now written as
21
+ `line:N%,end`, where the trailing `,end` is WebVTT's line alignment component and is what makes the
22
+ percentage refer to the bottom edge.
23
+
24
+ This also fixes `bottomOffset: 0`, which as a bare `line:100%` put the top of the cue box at the bottom
25
+ edge of the video and hid the subtitle completely. It now means "sit at the bottom", matching Android.
26
+
27
+ Worth knowing before upgrading: a WebVTT parser that predates the alignment component discards the
28
+ entire `line` setting rather than ignoring the component, so on such a parser captions would be left
29
+ unpositioned instead of top-anchored. Bottom anchoring cannot be expressed any other way — it is the
30
+ same setting — so confirm the offset still lands on your target OS versions.
31
+
32
+ - **iOS:** the cached rendition of a rewritten subtitle is keyed on the format version as well as on the
33
+ URL, language and offset. Without it, an install that had already rendered a given offset would be
34
+ handed the file written by the previous version and the fix above would appear not to work — on
35
+ exactly the devices being used to test it.
36
+
37
+ - **iOS:** the same cache key joins its parts on NUL instead of a hyphen. A hyphen occurs in both
38
+ variable parts — BCP-47 tags carry one (`pt-BR`) and so does almost every URL — so two different
39
+ subtitles could flatten to the same string and be served each other's rewritten file.
40
+
41
+ ### Documentation
42
+
43
+ - **Android:** `subtitleStyle.bottomOffset` was documented as a fraction of the *view* height. It is a
44
+ fraction of the content frame, which under the default `contentFit="contain"` is the displayed picture
45
+ — `SubtitleView` sits inside `AspectRatioFrameLayout`, which `RESIZE_MODE_FIT` shrinks to the video's
46
+ aspect ratio. The distinction matters for a letterboxed source, and the old wording invited callers to
47
+ write letterbox compensation that would have been wrong.
48
+
49
+ ## 0.4.0
50
+
51
+ ### Added
52
+
53
+ - **iOS:** `SubtitleSource.bottomOffset` lifts a sideloaded subtitle track off the bottom of the video,
54
+ as a fraction of its height. It is for releases that carry burned-in subtitles, where a sideloaded
55
+ track would otherwise be drawn on top of them. Android has had the same thing since 0.1.0 as
56
+ `subtitleStyle.bottomOffset`.
57
+
58
+ It lives on the source, not on `subtitleStyle`, because that is the only place iOS can read it from
59
+ in time. AVFoundation renders legible tracks itself and `AVPlayerItem.textStyleRules` carries no
60
+ positioning, so the position has to travel inside the WebVTT — written in as a `line:` cue setting
61
+ while the subtitle is merged into the video. That merge runs from the `VideoSource` alone, before any
62
+ `VideoView` has handed the player a style, so a view prop could not drive it without rebuilding the
63
+ player item every time a video starts. The two consequences are that it covers sideloaded tracks only
64
+ and that changing it takes effect on the next source load.
65
+
66
+ Cues that already position themselves are left alone, matching how Android passes explicitly
67
+ positioned cues through. Note the anchor differs between platforms: a bare WebVTT `line:` percentage
68
+ fixes the *top* of the cue box, where Android fixes the bottom, so a cue that wraps onto two lines
69
+ grows downward on iOS.
70
+
10
71
  ## 0.3.0
11
72
 
12
73
  ### Added
package/FORK.md CHANGED
@@ -6,7 +6,7 @@ and (b) pull a newer `expo-video` release into it **without losing the subtitle
6
6
  - **Baseline:** `expo-video@56.1.4` (Expo SDK 56), as published on npm.
7
7
  - **Native module name:** unchanged (`ExpoVideo`) — this is a drop-in replacement, not a coexisting package.
8
8
  - **Design principle:** the fork is deliberately **surgical and additive**. 7 new source files hold
9
- almost all of the logic (~1,200 lines); the 19 touched upstream files receive ~450 added lines and
9
+ almost all of the logic (~1,270 lines); the 19 touched upstream files receive ~470 added lines and
10
10
  only ~44 removed ones. Nothing upstream is rewritten — the removals are call-site signatures and a
11
11
  handful of rewritten function bodies — so a version bump is a small, mechanical re-apply.
12
12
 
@@ -20,9 +20,9 @@ Measured against the published `expo-video@56.1.4` tarball. `+`/`-` are added/re
20
20
 
21
21
  | File | Lines | Purpose |
22
22
  | --- | --- | --- |
23
- | `ios/Records/SubtitleSource.swift` | 32 | Record for one sideloaded subtitle; `isSrt` detection. |
23
+ | `ios/Records/SubtitleSource.swift` | 41 | Record for one sideloaded subtitle; `isSrt` detection; `bottomOffset`, which lives here rather than on `SubtitleStyle` because the composition is built from a `VideoSource` alone, before any `VideoView` has supplied a style. |
24
24
  | `ios/Records/SubtitleStyle.swift` | 118 | Record for caption styling; hex→ARGB parsing; builds `[AVTextStyleRule]`. |
25
- | `ios/VideoPlayerSubtitleSideload.swift` | 203 | Builds the `AVMutableComposition`, downloads remote subs, SRT→WebVTT converter. |
25
+ | `ios/VideoPlayerSubtitleSideload.swift` | 281 | Builds the `AVMutableComposition`, downloads remote subs, SRT→WebVTT converter, and `applyLinePosition`, which writes a WebVTT `line:N%,end` cue setting per cue for `bottomOffset` — the only way to position captions AVFoundation renders itself. **The trailing `,end` is the line alignment component, not a `line-align` setting** (there is no such setting; the six are `vertical`/`line`/`position`/`size`/`align`/`region`), and it is what anchors the percentage to the bottom edge, matching Android. A parser predating it drops the whole `line` setting, so there is no graceful degradation — verify on device. Cue timing lines are matched by **timestamp regex, not by searching for `-->`**: subtitle text may contain an arrow, and appending a cue setting to it prints the setting on screen. The temp-file hash includes `bottomOffset` *and* a `formatVersion`, joined on **NUL** — the offset so a changed value does not reuse the previous rendition, the version so an upgrade that changes the written form does not reuse the previous release's, and NUL because a hyphen occurs inside both BCP-47 tags and URLs and let two subtitles collide. |
26
26
  | `android/…/records/SubtitleSource.kt` | 54 | Record → `MediaItem.SubtitleConfiguration` + MIME resolution. |
27
27
  | `android/…/records/SubtitleStyle.kt` | 144 | Record → `CaptionStyleCompat`; hex colour parsing; font-family resolution (`res/font` → `ReactFontManager`); the `applyEmbeddedStyles` flag, which `SubtitleUtils` reads directly rather than `toCaptionStyle`. |
28
28
  | `android/…/utils/StackingSubtitleParser.kt` | 300 | `SubtitleParser` for SubRip that segments the cue timeline and merges simultaneous cues into one stacked block, plus the `SubtitleParser.Factory` that routes SubRip to it. Assigns each cue a vertical slot it keeps for its whole time on screen — a finished cue's line is held blank (`\u200B`) so the cues above it do not drop. Merges through a `SpannableStringBuilder` so cue spans survive. |
@@ -36,7 +36,7 @@ Measured against the published `expo-video@56.1.4` tarball. `+`/`-` are added/re
36
36
 
37
37
  | File | Δ | What to re-apply |
38
38
  | --- | --- | --- |
39
- | `src/VideoPlayer.types.ts` | +59 | `subtitleTracks?: SubtitleSource[]` on `VideoSourceObject`; the exported `SubtitleSource` type. |
39
+ | `src/VideoPlayer.types.ts` | +79 | `subtitleTracks?: SubtitleSource[]` on `VideoSourceObject`; the exported `SubtitleSource` type, including `bottomOffset`. |
40
40
  | `src/VideoView.types.ts` | +129 | `subtitleStyle?: SubtitleStyle` on `VideoViewProps`; `SubtitleStyle` + `SubtitleEdgeType` types. |
41
41
  | `src/index.ts` | +2 | Export `SubtitleStyle`, `SubtitleEdgeType` (`SubtitleSource` rides on `export type *`). |
42
42
  | `src/VideoPlayer.web.tsx` | +5 / −1 | Hoist `JSON.stringify(source)` out of the `useMemo` dependency list into a `sourceKey` const. No behaviour change — it was already recomputed every render — but `react-hooks/use-memo` rejects non-simple expressions in a dependency array, and it was the single lint **error** in the package. |
@@ -182,7 +182,8 @@ npm pack --dry-run # expect build/, plugin/build/, LICENSE, native sources
182
182
  # NO dev config (.prettierrc, eslint.config.js, *.tsbuildinfo) — see §1
183
183
  ```
184
184
 
185
- The file count is a blunt but effective regression check: **254 files** as of 0.2.2. If a change moves
185
+ The file count is a blunt but effective regression check: **255 files** as of 0.4.1 (254 at 0.2.2, plus
186
+ `RoundedSubtitleBackground.kt` in 0.3.0). If a change moves
186
187
  it, diff the list against the previous release and confirm every difference is intended —
187
188
  `npm pack --dry-run --json` gives a machine-readable list. Both directions matter: an unexpected
188
189
  *addition* means dev clutter leaked in, an unexpected *removal* means a consumer is missing something.
@@ -194,6 +195,36 @@ Neither is created by `expo-module configure`, so a sync that loses one breaks t
194
195
  that reads like a tooling bug. A `prettier/prettier` warning after a sync is far more likely to mean
195
196
  the config went missing than that the code is genuinely misformatted — check that first.
196
197
 
198
+ ### iOS — running the subtitle converter without a host app
199
+
200
+ `SubtitleConverter` at the bottom of `ios/VideoPlayerSubtitleSideload.swift` imports only `Foundation`,
201
+ so it compiles and runs on its own. It is worth exercising on every change to it: `srtToVtt` and
202
+ `applyLinePosition` are pure string transforms whose failure mode is a silently malformed cue rather
203
+ than a crash — a cue setting appended to a *text* line prints on screen, and a percentage outside
204
+ `0`–`100` can cost the whole cue.
205
+
206
+ Extract the shipped source rather than retyping it, so the test cannot drift from what ships:
207
+
208
+ ```bash
209
+ awk '/^internal enum SubtitleConverter \{/,0' ios/VideoPlayerSubtitleSideload.swift \
210
+ > /tmp/swiftcheck/Converter.swift
211
+ sed -i '' '1i\
212
+ import Foundation
213
+ ' /tmp/swiftcheck/Converter.swift
214
+ # Assertions go in main.swift — Swift allows top-level code only in a file with that name.
215
+ swiftc -o converttest Converter.swift main.swift && ./converttest
216
+ ```
217
+
218
+ Cases worth keeping covered: the emitted setting is `line:N%,end`; `0` and `1` map to `line:100%,end`
219
+ and `line:0%,end`; out-of-range offsets clamp; a text line containing `-->` is left alone; a cue already
220
+ carrying `line:` is passed through; an unrelated cue setting (`align:start`) is preserved while the cue
221
+ still moves; hourless timings are recognised; CRLF is normalised.
222
+
223
+ **A passing harness does not mean the cue renders where you asked.** It checks the bytes written, not
224
+ whether AVFoundation honours them. `line:` positioning is the part of this package with no automated
225
+ coverage at all — confirm it against a real player with a *two-line* cue, which is the case the
226
+ alignment component exists for.
227
+
197
228
  ### Android — compiling the parser without a host app
198
229
 
199
230
  `utils/StackingSubtitleParser.kt` imports only `android.text.*` and `androidx.media3.*`, so it
package/README.md CHANGED
@@ -67,7 +67,7 @@ const player = useVideoPlayer({
67
67
  player.subtitleTrack = player.availableSubtitleTracks[0];
68
68
  ```
69
69
 
70
- `SubtitleSource` fields: `uri` (local `file://` or remote `http(s)://`), `language` (BCP-47 / ISO 639), optional `label`, optional `format` (`'vtt' | 'srt'`, inferred from the extension when omitted), and optional `default`.
70
+ `SubtitleSource` fields: `uri` (local `file://` or remote `http(s)://`), `language` (BCP-47 / ISO 639), optional `label`, optional `format` (`'vtt' | 'srt'`, inferred from the extension when omitted), optional `default`, and optional `bottomOffset` (iOS — see [Moving subtitles off burned-in captions](#moving-subtitles-off-burned-in-captions)).
71
71
 
72
72
  | | Android | iOS |
73
73
  | --- | --- | --- |
@@ -109,6 +109,49 @@ The empty line can also sit *between* two cues when the middle one finishes firs
109
109
  left on screen, the stack resets and the next cue starts from the bottom again. This is Android-only;
110
110
  on iOS the system renderer decides the layout.
111
111
 
112
+ ### Moving subtitles off burned-in captions
113
+
114
+ Some releases have subtitles burned into the picture. A sideloaded track then lands in the same place
115
+ and the two overlap. Both platforms can lift the subtitles clear of them, but the setting lives in a
116
+ different place on each:
117
+
118
+ ```tsx
119
+ // Android — a VideoView prop, applies to every track, updates live
120
+ <VideoView player={player} subtitleStyle={{ bottomOffset: 0.22 }} />
121
+
122
+ // iOS — a field on the subtitle track itself
123
+ subtitleTracks: [{ uri: 'https://cdn.example.com/id.srt', language: 'id', bottomOffset: 0.22 }]
124
+ ```
125
+
126
+ Both are a fraction of the video height (`0`–`1`) measured up from the bottom; larger values sit
127
+ higher. The Android default is `0.08`.
128
+
129
+ The split is not an oversight. On iOS the position has to travel inside the WebVTT itself — the system
130
+ renders legible tracks and `AVPlayerItem.textStyleRules` carries no positioning — so it is written in
131
+ while the subtitle is merged into the video. That happens from the `VideoSource` alone, before any
132
+ `VideoView` has supplied a style, so a style prop could not reach it without rebuilding the player item
133
+ on every load. Two consequences on iOS: it applies to **sideloaded tracks only**, and **changing it
134
+ takes effect the next time the source loads**.
135
+
136
+ | | Android | iOS |
137
+ | --- | --- | --- |
138
+ | Where | `subtitleStyle.bottomOffset` | `subtitleTracks[].bottomOffset` |
139
+ | Applies to | every track, embedded included | sideloaded tracks only |
140
+ | Updates | live | on next source load |
141
+ | Anchors | the bottom of the subtitle block | the bottom of the subtitle block |
142
+ | Cues that position themselves | left alone | left alone |
143
+
144
+ That last row is worth knowing: subtitles carrying their own placement — SubRip `{\anN}` tags, WebVTT
145
+ cues with a `line:` setting — were positioned deliberately by whoever wrote them, so neither platform
146
+ moves them.
147
+
148
+ The iOS cue setting is written as `line:N%,end`. The trailing `,end` is WebVTT's line alignment
149
+ component, and it is what makes the percentage refer to the bottom edge of the cue box — so the two
150
+ platforms anchor the same way and one value is correct on both. Note that a WebVTT parser predating
151
+ that component discards the whole `line` setting rather than ignoring the component, which would leave
152
+ captions unpositioned; there is no form that degrades more gently, since both behaviours come from the
153
+ same setting.
154
+
112
155
  ## Styling subtitles
113
156
 
114
157
  Pass a `subtitleStyle` prop to `VideoView`. It applies to whichever subtitle track is currently displayed, whether embedded or sideloaded. Colors accept a hex string (`#RGB`, `#RRGGBB`, `#RRGGBBAA`) or `transparent`.
@@ -396,6 +396,25 @@ export type SubtitleSource = {
396
396
  * @default false
397
397
  */
398
398
  default?: boolean;
399
+ /**
400
+ * How far above the bottom of the video this track renders, as a fraction of the video height (0–1).
401
+ *
402
+ * Useful when a video already carries burned-in (hardcoded) subtitles and the sideloaded ones would
403
+ * otherwise be drawn on top of them.
404
+ *
405
+ * It sits here rather than on [`subtitleStyle`](#videoviewprops) because iOS can only apply it while
406
+ * the subtitle is being merged into the video, which happens from the `VideoSource` alone — before any
407
+ * `VideoView` has supplied a style. Changing it therefore takes effect the next time the source loads.
408
+ *
409
+ * > **Note:** cues that position themselves are left alone, so this does nothing for a WebVTT track
410
+ * > whose cues already carry a `line:` setting.
411
+ *
412
+ * On **Android** use [`subtitleStyle.bottomOffset`](#subtitlestylebottomoffset) instead, which does the
413
+ * same thing for every track at once and updates live.
414
+ *
415
+ * @platform ios
416
+ */
417
+ bottomOffset?: number;
399
418
  };
400
419
  /**
401
420
  * Contains information about any errors that the player encountered during the playback
@@ -1 +1 @@
1
- {"version":3,"file":"VideoPlayer.types.d.ts","sourceRoot":"","sources":["../src/VideoPlayer.types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"VideoPlayer.types.d.ts","sourceRoot":"","sources":["../src/VideoPlayer.types.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"VideoPlayer.types.js","sourceRoot":"","sources":["../src/VideoPlayer.types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC","sourcesContent":["import { SharedObject } from 'expo';\n\nimport type { VideoPlayerEvents } from './VideoPlayerEvents.types';\nimport type { VideoThumbnail } from './VideoThumbnail';\n\n/**\n * A class that represents an instance of the video player.\n */\nexport declare class VideoPlayer extends SharedObject<VideoPlayerEvents> {\n /**\n * Boolean value whether the player is currently playing.\n * > Use `play` and `pause` methods to control the playback.\n */\n readonly playing: boolean;\n\n /**\n * Determines whether the player should automatically replay after reaching the end of the video.\n * @default false\n */\n loop: boolean;\n\n /**\n * Determines whether the player should allow external playback.\n * @default true\n * @platform ios\n */\n allowsExternalPlayback: boolean;\n\n /**\n * Determines how the player will interact with other audio playing in the system.\n *\n * @default 'auto'\n * @platform android\n * @platform ios\n */\n audioMixingMode: AudioMixingMode;\n\n /**\n * Boolean value whether the player is currently muted.\n * Setting this property to `true`/`false` will mute/unmute the player.\n * @default false\n */\n muted: boolean;\n\n /**\n * Float value indicating the current playback time in seconds.\n *\n * If the player is not yet playing, this value indicates the time position\n * at which playback will begin once the `play()` method is called.\n *\n * Setting `currentTime` to a new value seeks the player to the given time.\n * Check out the [`seekTolerance`](#seektolerance) property to configure the seeking precision.\n */\n currentTime: number;\n\n /**\n * The exact timestamp when the currently displayed video frame was sent from the server,\n * based on the `EXT-X-PROGRAM-DATE-TIME` tag in the livestream metadata.\n * If this metadata is missing, this property will return `null`.\n * @platform android\n * @platform ios\n */\n readonly currentLiveTimestamp: number | null;\n\n /**\n * Float value indicating the latency of the live stream in seconds.\n * If a livestream doesn't have the required metadata, this will return `null`.\n * @platform android\n * @platform ios\n */\n readonly currentOffsetFromLive: number | null;\n\n /**\n * Float value indicating the time offset from the live in seconds.\n * @platform ios\n */\n targetOffsetFromLive: number;\n\n /**\n * Float value indicating the duration of the current video in seconds.\n */\n readonly duration: number;\n\n /**\n * Float value between `0` and `1.0` representing the current volume.\n * Muting the player doesn't affect the volume. In other words, when the player is muted, the volume is the same as\n * when unmuted. Similarly, setting the volume doesn't unmute the player.\n * @default 1.0\n */\n volume: number;\n\n /**\n * Boolean value indicating if the player should correct audio pitch when the playback speed changes.\n * @default true\n */\n preservesPitch: boolean;\n\n /**\n * Float value indicating the interval in seconds at which the player will emit the [`timeUpdate`](#videoplayerevents) event.\n * When the value is equal to `0`, the event will not be emitted.\n *\n * @default 0\n */\n timeUpdateEventInterval: number;\n\n /**\n * Float value between `0` and `16.0` indicating the current playback speed of the player.\n * @default 1.0\n */\n playbackRate: number;\n\n /**\n * Boolean indicating if the player should keep the screen on while playing.\n *\n * > On Android, this property has an effect only when a [`VideoView`](#videoview) is visible. If you want to keep the screen awake at all times use [`expo-keep-awake`](./keep-awake/).\n *\n * @default true\n * @platform android\n * @platform ios\n */\n keepScreenOnWhilePlaying: boolean;\n\n /**\n * Boolean value indicating whether the player is currently playing a live stream.\n */\n readonly isLive: boolean;\n\n /**\n * Indicates the current status of the player.\n */\n readonly status: VideoPlayerStatus;\n\n /**\n * Boolean value determining whether the player should show the now playing notification.\n *\n * > **Note**: On Android, `supportsBackgroundPlayback` property of the [config plugin](#configuration-in-app-config)\n * > has to be `true` for the now playing notification to work.\n * @default false\n * @platform android\n * @platform ios\n */\n showNowPlayingNotification: boolean;\n\n /**\n * Determines whether the player should continue playing after the app enters the background.\n *\n * > **Note**: The `supportsBackgroundPlayback` property of the [config plugin](#configuration-in-app-config)\n * > has to be `true` for the background playback to work.\n * @default false\n * @platform ios\n * @platform android\n */\n staysActiveInBackground: boolean;\n\n /**\n * Float value indicating how far the player has buffered the video in seconds.\n *\n * This value is 0 when the player has not buffered up to the current playback time.\n * When it's impossible to determine the buffer state (for example, when the player isn't playing any media), this value is -1.\n */\n readonly bufferedPosition: number;\n\n /**\n * Specifies buffer options which will be used by the player when buffering the video.\n *\n * > You should provide a `BufferOptions` object when setting this property. Setting individual buffer properties is not supported.\n * @platform android\n * @platform ios\n */\n bufferOptions: BufferOptions;\n\n /**\n * Specifies the subtitle track which is currently displayed by the player. `null` when no subtitles are displayed.\n *\n * > To ensure a valid subtitle track, always assign one of the subtitle tracks from the [`availableSubtitleTracks`](#availablesubtitletracks) array.\n *\n * @default null\n * @platform android\n * @platform ios\n */\n subtitleTrack: SubtitleTrack | null;\n\n /**\n * Specifies the audio track currently played by the player. `null` when no audio is played.\n *\n * @default null\n * @platform android\n * @platform ios\n */\n audioTrack: AudioTrack | null;\n\n /**\n * An array of audio tracks available for the current video.\n *\n * @platform android\n * @platform ios\n */\n readonly availableAudioTracks: AudioTrack[];\n\n /**\n * An array of subtitle tracks available for the current video.\n *\n * @platform android\n * @platform ios\n */\n readonly availableSubtitleTracks: SubtitleTrack[];\n\n /**\n * Specifies the video track currently played by the player. `null` when no video is displayed.\n *\n * @default null\n * @platform android\n * @platform ios\n */\n readonly videoTrack: VideoTrack | null;\n\n /**\n * An array of video tracks available for the current video.\n *\n * > On iOS, when using a HLS source, make sure that the uri contains `.m3u8` extension or that the [`contentType`](#contenttype) property of the [`VideoSource`](#videosource) has been set to `'hls'`. Otherwise, the video tracks will not be available.\n *\n * @platform android\n * @platform ios\n */\n readonly availableVideoTracks: VideoTrack[];\n\n /**\n * Indicates whether the player is currently playing back the media to an external device via AirPlay.\n *\n * @platform ios\n */\n readonly isExternalPlaybackActive: boolean;\n\n /**\n * Determines the time that the actual position seeked to may precede or exceed the requested seek position.\n *\n * This property affects the precision of setting the [`currentTime`](#currenttime) property and the [`seekBy`](#seekbyseconds) method, and on Android, it also affects the accuracy of the scrubber from the default native controls.\n *\n * By default, the player seeks to the exact requested time.\n *\n * > If you are trying to optimize for scrubbing (many frequent seeks), also see [`ScrubbingModeOptions`](#scrubbingmodeoptions-1).\n */\n seekTolerance: SeekTolerance;\n\n /**\n * Determines whether the scrubbing mode is enabled and what scrubbing optimizations should be enabled.\n *\n * > See [`SeekTolerance`](#seektolerance) to set the seeking tolerance, which can also affect the scrubbing performance.\n *\n */\n scrubbingModeOptions: ScrubbingModeOptions;\n\n /**\n * Initializes a new video player instance with the given source.\n *\n * @param source The source of the video to be played.\n * @param useSynchronousReplace Optional parameter, when `true` `source` from the first parameter will be loaded on the main thread.\n * @param playerBuilderOptions Options to apply to the player builder before the native constructor is invoked.\n * @hidden\n */\n constructor(\n source: VideoSource,\n useSynchronousReplace?: boolean,\n playerBuilderOptions?: PlayerBuilderOptions\n );\n\n /**\n * Resumes the player.\n */\n play(): void;\n\n /**\n * Pauses the player.\n */\n pause(): void;\n\n /**\n * Replaces the current source with a new one.\n *\n * > On iOS, this method loads the asset data synchronously on the UI thread and can block it for extended periods of time.\n * > Use `replaceAsync` to load the asset asynchronously and avoid UI lags.\n *\n * > This method will be deprecated in the future.\n */\n replace(source: VideoSource, disableWarning?: boolean): void;\n\n /**\n * Replaces the current source with a new one, while offloading loading of the asset to a different thread.\n *\n * > On Android and Web, this method is equivalent to `replace`.\n */\n replaceAsync(source: VideoSource): Promise<void>;\n\n /**\n * Seeks the playback by the given number of seconds. The time to which the player seeks may differ from the specified requested time for efficiency,\n * depending on the encoding and what is currently buffered by the player. Use this function to implement playback controls that seek by specific amount of time,\n * in which case, the actual time usually does not have to be precise. For frame accurate seeking, use the [`currentTime`](#currenttime) property.\n */\n seekBy(seconds: number): void;\n\n /**\n * Seeks the playback to the beginning.\n */\n replay(): void;\n\n /**\n * Generates thumbnails from the currently played asset. The thumbnails are references to native images,\n * thus they can be used as a source of the `Image` component from `expo-image`.\n * @platform android\n * @platform ios\n */\n generateThumbnailsAsync(\n times: number | number[],\n options?: VideoThumbnailOptions\n ): Promise<VideoThumbnail[]>;\n}\n\n/**\n * Additional options for video thumbnails generation.\n */\nexport type VideoThumbnailOptions = {\n /**\n * If provided, the generated thumbnail will not exceed this width in pixels, preserving its aspect ratio.\n * @platform android\n * @platform ios\n */\n maxWidth?: number;\n\n /**\n * If provided, the generated thumbnail will not exceed this height in pixels, preserving its aspect ratio.\n * @platform android\n * @platform ios\n */\n maxHeight?: number;\n};\n\n/**\n * Describes the current status of the player.\n * - `idle`: The player is not playing or loading any videos.\n * - `loading`: The player is loading video data from the provided source\n * - `readyToPlay`: The player has loaded enough data to start playing or to continue playback.\n * - `error`: The player has encountered an error while loading or playing the video.\n */\nexport type VideoPlayerStatus = 'idle' | 'loading' | 'readyToPlay' | 'error';\n\nexport type VideoSource = string | number | null | VideoSourceObject;\n\nexport type VideoSourceObject = {\n /**\n * The URI of the video.\n *\n * On iOS, `PHAsset` URIs are supported, but can only be loaded using the [`replaceAsync`](#replaceasyncsource) method or the default [`VideoPlayer`](#videoplayer) constructor.\n *\n * This property is exclusive with the `assetId` property. When both are present, the `assetId` will be ignored.\n */\n uri?: string;\n\n /**\n * The asset ID of a local video asset, acquired with the `require` function.\n * This property is exclusive with the `uri` property. When both are present, the `assetId` will be ignored.\n */\n assetId?: number;\n\n /**\n * Specifies the DRM options which will be used by the player while loading the video.\n */\n drm?: DRMOptions;\n\n /**\n * Specifies information which will be displayed in the now playing notification.\n * When undefined the player will display information contained in the video metadata.\n * @platform android\n * @platform ios\n */\n metadata?: VideoMetadata;\n\n /**\n * Specifies headers sent with the video request.\n * > For DRM license headers use the `headers` field of [`DRMOptions`](#drmoptions).\n * @platform android\n * @platform ios\n */\n headers?: Record<string, string>;\n\n /**\n * Specifies whether the player should use caching for the video.\n * > Due to platform limitations, the cache cannot be used with HLS video sources on iOS. Caching DRM-protected videos is not supported on Android and iOS.\n * @default false\n * @platform android\n * @platform ios\n */\n useCaching?: boolean;\n\n /**\n * Specifies the content type of the video source. When set to `'auto'`, the player will try to automatically determine the content type.\n *\n * You should use this property when playing HLS, SmoothStreaming or DASH videos from an uri, which does not contain a standardized extension for the corresponding media type.\n * @default 'auto'\n * @platform android\n * @platform ios\n */\n contentType?: ContentType;\n\n /**\n * An array of external (sideloaded) subtitle files to attach to the video source.\n *\n * Use this to add subtitles to sources that don't declare their own subtitle tracks — most importantly\n * progressive `MP4` files, which (unlike `HLS`/`DASH` manifests) cannot advertise external subtitle renditions.\n * The sideloaded tracks are merged into the media and become available through\n * [`availableSubtitleTracks`](#availablesubtitletracks), so they can be selected with\n * [`subtitleTrack`](#subtitletrack) just like embedded tracks.\n *\n * > **Note (iOS):** sideloading is done by building an `AVMutableComposition`, which has platform limitations:\n * > the video must be a progressive (non-HLS) source without DRM, remote subtitle files are downloaded to a\n * > local cache first, and only `WebVTT` is natively supported (`SRT` files are converted to `WebVTT`\n * > automatically). Sideloading only runs on the asynchronous loading path (the default `VideoPlayer`\n * > constructor and [`replaceAsync`](#replaceasyncsource)), not on the synchronous `replace`.\n *\n * @platform android\n * @platform ios\n */\n subtitleTracks?: SubtitleSource[];\n};\n\n/**\n * Describes a single external (sideloaded) subtitle file that can be attached to a [`VideoSource`](#videosource).\n */\nexport type SubtitleSource = {\n /**\n * The URI of the subtitle file. Both local (`file://`) and remote (`http(s)://`) URIs are supported.\n */\n uri: string;\n\n /**\n * The BCP-47 / ISO 639 language code of the subtitle track, for example `en`, `pl`, `de`.\n * This value is used to identify and select the track and is shown in the native track selector.\n */\n language: string;\n\n /**\n * A human-readable label for the track, shown in the native subtitle selection UI.\n * When omitted, a label is derived from the `language`.\n */\n label?: string;\n\n /**\n * The format of the subtitle file.\n * When omitted, the format is inferred from the file extension (`.vtt` / `.srt`).\n *\n * - `vtt`: WebVTT subtitles (`text/vtt`).\n * - `srt`: SubRip subtitles (`application/x-subrip`). On iOS these are converted to WebVTT before sideloading.\n *\n * @default inferred from the uri extension\n */\n format?: 'vtt' | 'srt';\n\n /**\n * Whether this track should be marked as the default subtitle track.\n * @default false\n */\n default?: boolean;\n};\n\n/**\n * Contains information about any errors that the player encountered during the playback\n */\nexport type PlayerError = {\n message: string;\n};\n\n/**\n * Contains information that will be displayed in the now playing notification when the video is playing.\n * @platform android\n * @platform ios\n */\nexport type VideoMetadata = {\n /**\n * The title of the video.\n * @platform android\n * @platform ios\n */\n title?: string;\n /**\n * Secondary text that will be displayed under the title.\n * @platform android\n * @platform ios\n */\n artist?: string;\n /**\n * The uri of the video artwork.\n * @platform android\n * @platform ios\n */\n artwork?: string;\n};\n\n/**\n * Specifies which type of DRM to use:\n * - Android supports ClearKey, PlayReady and Widevine.\n * - iOS supports FairPlay.\n */\nexport type DRMType = 'clearkey' | 'fairplay' | 'playready' | 'widevine';\n\n/**\n * Specifies DRM options which will be used by the player while loading the video.\n */\nexport type DRMOptions = {\n /**\n * Determines which type of DRM to use.\n */\n type: DRMType;\n\n /**\n * Determines the license server URL.\n */\n licenseServer: string;\n\n /**\n * Determines headers sent to the license server on license requests.\n */\n headers?: Record<string, string>;\n\n /**\n * Specifies whether the DRM is a multi-key DRM.\n * @platform android\n */\n multiKey?: boolean;\n\n /**\n * Specifies the content ID of the stream.\n * @platform ios\n */\n contentId?: string;\n\n /**\n * Specifies the certificate URL for the FairPlay DRM.\n * @platform ios\n */\n certificateUrl?: string;\n\n /**\n * Specifies the base64 encoded certificate data for the FairPlay DRM.\n * When this property is set, the `certificateUrl` property is ignored.\n * @platform ios\n */\n base64CertificateData?: string;\n};\n\n/**\n * Specifies buffer options which will be used by the player when buffering the video.\n *\n * @platform android\n * @platform ios\n */\nexport type BufferOptions = {\n /**\n * The duration in seconds which determines how much media the player should buffer ahead of the current playback time.\n *\n * On iOS when set to `0` the player will automatically decide appropriate buffer duration.\n *\n * Equivalent to [`AVPlayerItem.preferredForwardBufferDuration`](https://developer.apple.com/documentation/avfoundation/avplayeritem/1643630-preferredforwardbufferduration).\n * @default Android: 20, iOS: 0\n * @platform android\n * @platform ios\n */\n readonly preferredForwardBufferDuration?: number;\n\n /**\n * A Boolean value that indicates whether the player should automatically delay playback in order to minimize stalling.\n *\n * Equivalent to [`AVPlayer.automaticallyWaitsToMinimizeStalling`](https://developer.apple.com/documentation/avfoundation/avplayer/1643482-automaticallywaitstominimizestal).\n * @default true\n * @platform ios\n */\n readonly waitsToMinimizeStalling?: boolean;\n\n /**\n * Minimum duration of the buffer in seconds required to continue playing after the player has been paused or started buffering.\n *\n * > This property will be ignored if `preferredForwardBufferDuration` is lower.\n * @default 2\n * @platform android\n */\n readonly minBufferForPlayback?: number;\n\n /**\n * The maximum number of bytes that the player can buffer from the network.\n * When 0 the player will automatically decide appropriate buffer size.\n *\n * @default 0\n * @platform android\n */\n readonly maxBufferBytes?: number | null;\n\n /**\n * A Boolean value which determines whether the player should prioritize time over size when buffering media.\n *\n * @default false\n * @platform android\n */\n readonly prioritizeTimeOverSizeThreshold?: boolean;\n};\n\n/**\n * Specifies the content type of the source.\n *\n * - `auto`: The player will automatically determine the content type of the video.\n * - `progressive`: The player will use progressive download content type. This is the default `ContentType` when the uri does not contain an extension.\n * - `hls`: The player will use HLS content type.\n * - `dash`: The player will use DASH content type (Android-only).\n * - `smoothStreaming`: The player will use SmoothStreaming content type (Android-only).\n *\n * @default `auto`\n */\nexport type ContentType = 'auto' | 'progressive' | 'hls' | 'dash' | 'smoothStreaming';\n\n/**\n * Specifies the audio mode that the player should use. Audio mode is set on per-app basis, if there are multiple players playing and\n * have different a `AudioMode` specified, the highest priority mode will be used. Priority order: 'doNotMix' > 'auto' > 'duckOthers' > 'mixWithOthers'.\n *\n * - `mixWithOthers`: The player will mix its audio output with other apps.\n * - `duckOthers`: The player will lower the volume of other apps if any of the active players is outputting audio.\n * - `auto`: The player will allow other apps to keep playing audio only when it is muted. On iOS it will always interrupt other apps when `showNowPlayingNotification` is `true` due to system requirements.\n * - `doNotMix`: The player will pause playback in other apps, even when it's muted.\n *\n * > On iOS, the Now Playing notification is dependent on the audio mode. If the audio mode is different from `doNotMix` or `auto` this feature will not work.\n */\nexport type AudioMixingMode = 'mixWithOthers' | 'duckOthers' | 'auto' | 'doNotMix';\n\nexport type SubtitleTrack = {\n /**\n * A string used by `expo-video` to identify the subtitle track.\n *\n * @platform android\n */\n id?: string;\n\n /**\n * Language of the subtitle track. For example, `en`, `pl`, `de`.\n */\n language: string;\n\n /**\n * Label of the subtitle track in the language of the device.\n */\n label: string;\n\n /**\n * Name of the subtitle track as specified in the media source.\n * @platform android\n * @platform ios\n */\n name?: string;\n\n /**\n * Indicates whether this is the default subtitle track.\n * @platform android\n * @platform ios\n */\n isDefault?: boolean;\n\n /**\n * Indicates whether this track should be auto-selected based on user preferences.\n * @platform android\n * @platform ios\n */\n autoSelect?: boolean;\n};\n\n/**\n * Specifies a VideoTrack loaded from a [`VideoSource`](#videosource).\n */\nexport type VideoTrack = {\n /**\n * The id of the video track.\n *\n * > This field is platform-specific and may return different depending on the operating system.\n */\n id: string;\n\n /**\n * The URL of the `VideoTrack` for HLS video sources. `null` for other source types.\n */\n url: string | null;\n\n /**\n * Size of the video track.\n */\n size: VideoSize;\n\n /**\n * MimeType of the video track or null if unknown.\n */\n mimeType: string | null;\n\n /**\n * Indicates whether the video track format is supported by the device.\n *\n * @platform android\n */\n isSupported: boolean;\n\n /**\n * Specifies the bitrate in bits per second. This is the peak bitrate if known, or else the average bitrate if known, or else null.\n *\n * @deprecated Use `peakBitrate` or `averageBitrate` instead.\n */\n bitrate: number | null;\n\n /**\n * Specifies the average bitrate in bits per second or null if the value is unknown.\n *\n */\n averageBitrate: number | null;\n\n /**\n * Specifies the average bitrate in bits per second or null if the value is unknown.\n */\n peakBitrate: number | null;\n\n /**\n * Specifies the frame rate of the video track in frames per second.\n */\n frameRate: number | null;\n\n /**\n * Specifies the video range of the video track.\n */\n videoRange: VideoRange;\n};\n\n/**\n * Specifies the size of a video track.\n */\nexport type VideoSize = {\n /**\n * Width of the video track in pixels.\n */\n width: number;\n /**\n * Height of the video track in pixels.\n */\n height: number;\n};\n\nexport type AudioTrack = {\n /**\n * A string used by expo-video to identify the audio track.\n * @platform android\n */\n id?: string;\n\n /**\n * Language of the audio track. For example, 'en', 'pl', 'de'.\n */\n language: string;\n\n /**\n * Label of the audio track in the language of the device.\n */\n label: string;\n\n /**\n * Name of the audio track as specified in the media source.\n * @platform android\n * @platform ios\n */\n name?: string;\n\n /**\n * Indicates whether this is the default audio track.\n * @platform android\n * @platform ios\n */\n isDefault?: boolean;\n\n /**\n * Indicates whether this track should be auto-selected based on user preferences.\n * @platform android\n * @platform ios\n */\n autoSelect?: boolean;\n};\n\n/**\n * Determines the time that the actual position seeked to may precede or exceed the requested seek position.\n * Larger tolerance will usually result in faster seeking.\n * This property affects the precision of setting the [`currentTime`](#currenttime) property and the [`seekBy`](#seekbyseconds) method, and on Android, it also affects the accuracy of the scrubber from the default native controls.\n *\n * > If you are trying to optimize for scrubbing (many frequent seeks), also see [`ScrubbingModeOptions`](#scrubbingmodeoptions-1).\n *\n * @platform android\n * @platform ios\n */\nexport type SeekTolerance = {\n /**\n * The maximum time that the actual position seeked to may precede the requested seek position, in seconds. Must be non-negative.\n * @default 0\n */\n toleranceBefore?: number;\n\n /**\n * The maximum time that the actual position seeked to may exceed the requested seek position, in seconds. Must be non-negative.\n * @default 0\n */\n toleranceAfter?: number;\n};\n\n/**\n * Defines scrubbing mode options used by a [`VideoPlayer`](#videoplayer).\n */\nexport type ScrubbingModeOptions = {\n /**\n * Whether the codec operating rate should be increased in scrubbing mode.\n *\n * You should only enable this when the player is receiving a large number of seeks in a short period of time. For less frequent seeks, fine-tuning the [`SeekTolerance`](#seektolerance-1) may be sufficient.\n *\n * On Android, the player may consume more resources in this mode, so it should only be used for short periods of time in response to user interaction (for example, dragging on a progress bar UI element).\n *\n * On Android, when `scrubbingModeEnabled` is `true`, the playback is suppressed. You should set this property back to `false` when the user interaction ends to allow the playback to resume.\n * For best results, on iOS you should pause the playback when scrubbing.\n *\n * > For best scrubbing performance, consider also increasing the seeking tolerance using the [`SeekTolerance`](#seektolerance-1) property.\n *\n * > Other scrubbing mode options will have no effect when this is `false`.\n * @default false\n * @platform android\n * @platform ios\n */\n scrubbingModeEnabled?: boolean;\n\n /**\n * Whether the codec operating rate should be increased in scrubbing mode.\n *\n * @platform android\n * @default true\n */\n increaseCodecOperatingRate?: boolean;\n\n /**\n * Sets whether ExoPlayer's dynamic scheduling should be enabled in scrubbing mode.\n * This can result in available output buffers being handled more quickly when seeking.\n *\n * @platform android\n * @default true\n */\n enableDynamicScheduling?: boolean;\n /**\n * Sets whether to use `MediaCodec.BUFFER_FLAG_DECODE_ONLY` in scrubbing mode.\n * When playback is using MediaCodec on API 34+, this flag can speed up seeking by signalling that the decoded output of buffers between the previous keyframe and the target frame is not needed by the player.\n *\n * @platform android\n * @default true\n */\n useDecodeOnlyFlag?: boolean;\n\n /**\n * Sets whether to avoid flushing the decoder (where possible) in scrubbing mode.\n * When `true`, avoids flushing the decoder when a new seek starts decoding from a key-frame in compatible content.\n *\n * @platform android\n * @default true\n */\n allowSkippingMediaCodecFlush?: boolean;\n};\n\n/**\n * Options to apply to the player builder before the native constructor is invoked\n * @platform android\n */\nexport type PlayerBuilderOptions = {\n /**\n * Seek backward increment in seconds.\n * Values will be clamped between 0.001 and 999 seconds.\n * @platform android\n */\n seekBackwardIncrement?: number;\n\n /**\n * Seek forward increment in seconds.\n * Values will be clamped between 0.001 and 999 seconds.\n * @platform android\n */\n seekForwardIncrement?: number;\n};\n\n/**\n * Specifies the dynamic range of the video content.\n * - `sdr`: Standard Dynamic Range video.\n * - `hlg`: Hybrid Log-Gamma - HDR backward-compatible with SDR displays\n * - `pq`: Perceptual Quantizer - Formats like HDR10 and Dolby Vision\n */\nexport type VideoRange = 'sdr' | 'hlg' | 'pq';\n"]}
1
+ {"version":3,"file":"VideoPlayer.types.js","sourceRoot":"","sources":["../src/VideoPlayer.types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC","sourcesContent":["import { SharedObject } from 'expo';\n\nimport type { VideoPlayerEvents } from './VideoPlayerEvents.types';\nimport type { VideoThumbnail } from './VideoThumbnail';\n\n/**\n * A class that represents an instance of the video player.\n */\nexport declare class VideoPlayer extends SharedObject<VideoPlayerEvents> {\n /**\n * Boolean value whether the player is currently playing.\n * > Use `play` and `pause` methods to control the playback.\n */\n readonly playing: boolean;\n\n /**\n * Determines whether the player should automatically replay after reaching the end of the video.\n * @default false\n */\n loop: boolean;\n\n /**\n * Determines whether the player should allow external playback.\n * @default true\n * @platform ios\n */\n allowsExternalPlayback: boolean;\n\n /**\n * Determines how the player will interact with other audio playing in the system.\n *\n * @default 'auto'\n * @platform android\n * @platform ios\n */\n audioMixingMode: AudioMixingMode;\n\n /**\n * Boolean value whether the player is currently muted.\n * Setting this property to `true`/`false` will mute/unmute the player.\n * @default false\n */\n muted: boolean;\n\n /**\n * Float value indicating the current playback time in seconds.\n *\n * If the player is not yet playing, this value indicates the time position\n * at which playback will begin once the `play()` method is called.\n *\n * Setting `currentTime` to a new value seeks the player to the given time.\n * Check out the [`seekTolerance`](#seektolerance) property to configure the seeking precision.\n */\n currentTime: number;\n\n /**\n * The exact timestamp when the currently displayed video frame was sent from the server,\n * based on the `EXT-X-PROGRAM-DATE-TIME` tag in the livestream metadata.\n * If this metadata is missing, this property will return `null`.\n * @platform android\n * @platform ios\n */\n readonly currentLiveTimestamp: number | null;\n\n /**\n * Float value indicating the latency of the live stream in seconds.\n * If a livestream doesn't have the required metadata, this will return `null`.\n * @platform android\n * @platform ios\n */\n readonly currentOffsetFromLive: number | null;\n\n /**\n * Float value indicating the time offset from the live in seconds.\n * @platform ios\n */\n targetOffsetFromLive: number;\n\n /**\n * Float value indicating the duration of the current video in seconds.\n */\n readonly duration: number;\n\n /**\n * Float value between `0` and `1.0` representing the current volume.\n * Muting the player doesn't affect the volume. In other words, when the player is muted, the volume is the same as\n * when unmuted. Similarly, setting the volume doesn't unmute the player.\n * @default 1.0\n */\n volume: number;\n\n /**\n * Boolean value indicating if the player should correct audio pitch when the playback speed changes.\n * @default true\n */\n preservesPitch: boolean;\n\n /**\n * Float value indicating the interval in seconds at which the player will emit the [`timeUpdate`](#videoplayerevents) event.\n * When the value is equal to `0`, the event will not be emitted.\n *\n * @default 0\n */\n timeUpdateEventInterval: number;\n\n /**\n * Float value between `0` and `16.0` indicating the current playback speed of the player.\n * @default 1.0\n */\n playbackRate: number;\n\n /**\n * Boolean indicating if the player should keep the screen on while playing.\n *\n * > On Android, this property has an effect only when a [`VideoView`](#videoview) is visible. If you want to keep the screen awake at all times use [`expo-keep-awake`](./keep-awake/).\n *\n * @default true\n * @platform android\n * @platform ios\n */\n keepScreenOnWhilePlaying: boolean;\n\n /**\n * Boolean value indicating whether the player is currently playing a live stream.\n */\n readonly isLive: boolean;\n\n /**\n * Indicates the current status of the player.\n */\n readonly status: VideoPlayerStatus;\n\n /**\n * Boolean value determining whether the player should show the now playing notification.\n *\n * > **Note**: On Android, `supportsBackgroundPlayback` property of the [config plugin](#configuration-in-app-config)\n * > has to be `true` for the now playing notification to work.\n * @default false\n * @platform android\n * @platform ios\n */\n showNowPlayingNotification: boolean;\n\n /**\n * Determines whether the player should continue playing after the app enters the background.\n *\n * > **Note**: The `supportsBackgroundPlayback` property of the [config plugin](#configuration-in-app-config)\n * > has to be `true` for the background playback to work.\n * @default false\n * @platform ios\n * @platform android\n */\n staysActiveInBackground: boolean;\n\n /**\n * Float value indicating how far the player has buffered the video in seconds.\n *\n * This value is 0 when the player has not buffered up to the current playback time.\n * When it's impossible to determine the buffer state (for example, when the player isn't playing any media), this value is -1.\n */\n readonly bufferedPosition: number;\n\n /**\n * Specifies buffer options which will be used by the player when buffering the video.\n *\n * > You should provide a `BufferOptions` object when setting this property. Setting individual buffer properties is not supported.\n * @platform android\n * @platform ios\n */\n bufferOptions: BufferOptions;\n\n /**\n * Specifies the subtitle track which is currently displayed by the player. `null` when no subtitles are displayed.\n *\n * > To ensure a valid subtitle track, always assign one of the subtitle tracks from the [`availableSubtitleTracks`](#availablesubtitletracks) array.\n *\n * @default null\n * @platform android\n * @platform ios\n */\n subtitleTrack: SubtitleTrack | null;\n\n /**\n * Specifies the audio track currently played by the player. `null` when no audio is played.\n *\n * @default null\n * @platform android\n * @platform ios\n */\n audioTrack: AudioTrack | null;\n\n /**\n * An array of audio tracks available for the current video.\n *\n * @platform android\n * @platform ios\n */\n readonly availableAudioTracks: AudioTrack[];\n\n /**\n * An array of subtitle tracks available for the current video.\n *\n * @platform android\n * @platform ios\n */\n readonly availableSubtitleTracks: SubtitleTrack[];\n\n /**\n * Specifies the video track currently played by the player. `null` when no video is displayed.\n *\n * @default null\n * @platform android\n * @platform ios\n */\n readonly videoTrack: VideoTrack | null;\n\n /**\n * An array of video tracks available for the current video.\n *\n * > On iOS, when using a HLS source, make sure that the uri contains `.m3u8` extension or that the [`contentType`](#contenttype) property of the [`VideoSource`](#videosource) has been set to `'hls'`. Otherwise, the video tracks will not be available.\n *\n * @platform android\n * @platform ios\n */\n readonly availableVideoTracks: VideoTrack[];\n\n /**\n * Indicates whether the player is currently playing back the media to an external device via AirPlay.\n *\n * @platform ios\n */\n readonly isExternalPlaybackActive: boolean;\n\n /**\n * Determines the time that the actual position seeked to may precede or exceed the requested seek position.\n *\n * This property affects the precision of setting the [`currentTime`](#currenttime) property and the [`seekBy`](#seekbyseconds) method, and on Android, it also affects the accuracy of the scrubber from the default native controls.\n *\n * By default, the player seeks to the exact requested time.\n *\n * > If you are trying to optimize for scrubbing (many frequent seeks), also see [`ScrubbingModeOptions`](#scrubbingmodeoptions-1).\n */\n seekTolerance: SeekTolerance;\n\n /**\n * Determines whether the scrubbing mode is enabled and what scrubbing optimizations should be enabled.\n *\n * > See [`SeekTolerance`](#seektolerance) to set the seeking tolerance, which can also affect the scrubbing performance.\n *\n */\n scrubbingModeOptions: ScrubbingModeOptions;\n\n /**\n * Initializes a new video player instance with the given source.\n *\n * @param source The source of the video to be played.\n * @param useSynchronousReplace Optional parameter, when `true` `source` from the first parameter will be loaded on the main thread.\n * @param playerBuilderOptions Options to apply to the player builder before the native constructor is invoked.\n * @hidden\n */\n constructor(\n source: VideoSource,\n useSynchronousReplace?: boolean,\n playerBuilderOptions?: PlayerBuilderOptions\n );\n\n /**\n * Resumes the player.\n */\n play(): void;\n\n /**\n * Pauses the player.\n */\n pause(): void;\n\n /**\n * Replaces the current source with a new one.\n *\n * > On iOS, this method loads the asset data synchronously on the UI thread and can block it for extended periods of time.\n * > Use `replaceAsync` to load the asset asynchronously and avoid UI lags.\n *\n * > This method will be deprecated in the future.\n */\n replace(source: VideoSource, disableWarning?: boolean): void;\n\n /**\n * Replaces the current source with a new one, while offloading loading of the asset to a different thread.\n *\n * > On Android and Web, this method is equivalent to `replace`.\n */\n replaceAsync(source: VideoSource): Promise<void>;\n\n /**\n * Seeks the playback by the given number of seconds. The time to which the player seeks may differ from the specified requested time for efficiency,\n * depending on the encoding and what is currently buffered by the player. Use this function to implement playback controls that seek by specific amount of time,\n * in which case, the actual time usually does not have to be precise. For frame accurate seeking, use the [`currentTime`](#currenttime) property.\n */\n seekBy(seconds: number): void;\n\n /**\n * Seeks the playback to the beginning.\n */\n replay(): void;\n\n /**\n * Generates thumbnails from the currently played asset. The thumbnails are references to native images,\n * thus they can be used as a source of the `Image` component from `expo-image`.\n * @platform android\n * @platform ios\n */\n generateThumbnailsAsync(\n times: number | number[],\n options?: VideoThumbnailOptions\n ): Promise<VideoThumbnail[]>;\n}\n\n/**\n * Additional options for video thumbnails generation.\n */\nexport type VideoThumbnailOptions = {\n /**\n * If provided, the generated thumbnail will not exceed this width in pixels, preserving its aspect ratio.\n * @platform android\n * @platform ios\n */\n maxWidth?: number;\n\n /**\n * If provided, the generated thumbnail will not exceed this height in pixels, preserving its aspect ratio.\n * @platform android\n * @platform ios\n */\n maxHeight?: number;\n};\n\n/**\n * Describes the current status of the player.\n * - `idle`: The player is not playing or loading any videos.\n * - `loading`: The player is loading video data from the provided source\n * - `readyToPlay`: The player has loaded enough data to start playing or to continue playback.\n * - `error`: The player has encountered an error while loading or playing the video.\n */\nexport type VideoPlayerStatus = 'idle' | 'loading' | 'readyToPlay' | 'error';\n\nexport type VideoSource = string | number | null | VideoSourceObject;\n\nexport type VideoSourceObject = {\n /**\n * The URI of the video.\n *\n * On iOS, `PHAsset` URIs are supported, but can only be loaded using the [`replaceAsync`](#replaceasyncsource) method or the default [`VideoPlayer`](#videoplayer) constructor.\n *\n * This property is exclusive with the `assetId` property. When both are present, the `assetId` will be ignored.\n */\n uri?: string;\n\n /**\n * The asset ID of a local video asset, acquired with the `require` function.\n * This property is exclusive with the `uri` property. When both are present, the `assetId` will be ignored.\n */\n assetId?: number;\n\n /**\n * Specifies the DRM options which will be used by the player while loading the video.\n */\n drm?: DRMOptions;\n\n /**\n * Specifies information which will be displayed in the now playing notification.\n * When undefined the player will display information contained in the video metadata.\n * @platform android\n * @platform ios\n */\n metadata?: VideoMetadata;\n\n /**\n * Specifies headers sent with the video request.\n * > For DRM license headers use the `headers` field of [`DRMOptions`](#drmoptions).\n * @platform android\n * @platform ios\n */\n headers?: Record<string, string>;\n\n /**\n * Specifies whether the player should use caching for the video.\n * > Due to platform limitations, the cache cannot be used with HLS video sources on iOS. Caching DRM-protected videos is not supported on Android and iOS.\n * @default false\n * @platform android\n * @platform ios\n */\n useCaching?: boolean;\n\n /**\n * Specifies the content type of the video source. When set to `'auto'`, the player will try to automatically determine the content type.\n *\n * You should use this property when playing HLS, SmoothStreaming or DASH videos from an uri, which does not contain a standardized extension for the corresponding media type.\n * @default 'auto'\n * @platform android\n * @platform ios\n */\n contentType?: ContentType;\n\n /**\n * An array of external (sideloaded) subtitle files to attach to the video source.\n *\n * Use this to add subtitles to sources that don't declare their own subtitle tracks — most importantly\n * progressive `MP4` files, which (unlike `HLS`/`DASH` manifests) cannot advertise external subtitle renditions.\n * The sideloaded tracks are merged into the media and become available through\n * [`availableSubtitleTracks`](#availablesubtitletracks), so they can be selected with\n * [`subtitleTrack`](#subtitletrack) just like embedded tracks.\n *\n * > **Note (iOS):** sideloading is done by building an `AVMutableComposition`, which has platform limitations:\n * > the video must be a progressive (non-HLS) source without DRM, remote subtitle files are downloaded to a\n * > local cache first, and only `WebVTT` is natively supported (`SRT` files are converted to `WebVTT`\n * > automatically). Sideloading only runs on the asynchronous loading path (the default `VideoPlayer`\n * > constructor and [`replaceAsync`](#replaceasyncsource)), not on the synchronous `replace`.\n *\n * @platform android\n * @platform ios\n */\n subtitleTracks?: SubtitleSource[];\n};\n\n/**\n * Describes a single external (sideloaded) subtitle file that can be attached to a [`VideoSource`](#videosource).\n */\nexport type SubtitleSource = {\n /**\n * The URI of the subtitle file. Both local (`file://`) and remote (`http(s)://`) URIs are supported.\n */\n uri: string;\n\n /**\n * The BCP-47 / ISO 639 language code of the subtitle track, for example `en`, `pl`, `de`.\n * This value is used to identify and select the track and is shown in the native track selector.\n */\n language: string;\n\n /**\n * A human-readable label for the track, shown in the native subtitle selection UI.\n * When omitted, a label is derived from the `language`.\n */\n label?: string;\n\n /**\n * The format of the subtitle file.\n * When omitted, the format is inferred from the file extension (`.vtt` / `.srt`).\n *\n * - `vtt`: WebVTT subtitles (`text/vtt`).\n * - `srt`: SubRip subtitles (`application/x-subrip`). On iOS these are converted to WebVTT before sideloading.\n *\n * @default inferred from the uri extension\n */\n format?: 'vtt' | 'srt';\n\n /**\n * Whether this track should be marked as the default subtitle track.\n * @default false\n */\n default?: boolean;\n\n /**\n * How far above the bottom of the video this track renders, as a fraction of the video height (0–1).\n *\n * Useful when a video already carries burned-in (hardcoded) subtitles and the sideloaded ones would\n * otherwise be drawn on top of them.\n *\n * It sits here rather than on [`subtitleStyle`](#videoviewprops) because iOS can only apply it while\n * the subtitle is being merged into the video, which happens from the `VideoSource` alone — before any\n * `VideoView` has supplied a style. Changing it therefore takes effect the next time the source loads.\n *\n * > **Note:** cues that position themselves are left alone, so this does nothing for a WebVTT track\n * > whose cues already carry a `line:` setting.\n *\n * On **Android** use [`subtitleStyle.bottomOffset`](#subtitlestylebottomoffset) instead, which does the\n * same thing for every track at once and updates live.\n *\n * @platform ios\n */\n bottomOffset?: number;\n};\n\n/**\n * Contains information about any errors that the player encountered during the playback\n */\nexport type PlayerError = {\n message: string;\n};\n\n/**\n * Contains information that will be displayed in the now playing notification when the video is playing.\n * @platform android\n * @platform ios\n */\nexport type VideoMetadata = {\n /**\n * The title of the video.\n * @platform android\n * @platform ios\n */\n title?: string;\n /**\n * Secondary text that will be displayed under the title.\n * @platform android\n * @platform ios\n */\n artist?: string;\n /**\n * The uri of the video artwork.\n * @platform android\n * @platform ios\n */\n artwork?: string;\n};\n\n/**\n * Specifies which type of DRM to use:\n * - Android supports ClearKey, PlayReady and Widevine.\n * - iOS supports FairPlay.\n */\nexport type DRMType = 'clearkey' | 'fairplay' | 'playready' | 'widevine';\n\n/**\n * Specifies DRM options which will be used by the player while loading the video.\n */\nexport type DRMOptions = {\n /**\n * Determines which type of DRM to use.\n */\n type: DRMType;\n\n /**\n * Determines the license server URL.\n */\n licenseServer: string;\n\n /**\n * Determines headers sent to the license server on license requests.\n */\n headers?: Record<string, string>;\n\n /**\n * Specifies whether the DRM is a multi-key DRM.\n * @platform android\n */\n multiKey?: boolean;\n\n /**\n * Specifies the content ID of the stream.\n * @platform ios\n */\n contentId?: string;\n\n /**\n * Specifies the certificate URL for the FairPlay DRM.\n * @platform ios\n */\n certificateUrl?: string;\n\n /**\n * Specifies the base64 encoded certificate data for the FairPlay DRM.\n * When this property is set, the `certificateUrl` property is ignored.\n * @platform ios\n */\n base64CertificateData?: string;\n};\n\n/**\n * Specifies buffer options which will be used by the player when buffering the video.\n *\n * @platform android\n * @platform ios\n */\nexport type BufferOptions = {\n /**\n * The duration in seconds which determines how much media the player should buffer ahead of the current playback time.\n *\n * On iOS when set to `0` the player will automatically decide appropriate buffer duration.\n *\n * Equivalent to [`AVPlayerItem.preferredForwardBufferDuration`](https://developer.apple.com/documentation/avfoundation/avplayeritem/1643630-preferredforwardbufferduration).\n * @default Android: 20, iOS: 0\n * @platform android\n * @platform ios\n */\n readonly preferredForwardBufferDuration?: number;\n\n /**\n * A Boolean value that indicates whether the player should automatically delay playback in order to minimize stalling.\n *\n * Equivalent to [`AVPlayer.automaticallyWaitsToMinimizeStalling`](https://developer.apple.com/documentation/avfoundation/avplayer/1643482-automaticallywaitstominimizestal).\n * @default true\n * @platform ios\n */\n readonly waitsToMinimizeStalling?: boolean;\n\n /**\n * Minimum duration of the buffer in seconds required to continue playing after the player has been paused or started buffering.\n *\n * > This property will be ignored if `preferredForwardBufferDuration` is lower.\n * @default 2\n * @platform android\n */\n readonly minBufferForPlayback?: number;\n\n /**\n * The maximum number of bytes that the player can buffer from the network.\n * When 0 the player will automatically decide appropriate buffer size.\n *\n * @default 0\n * @platform android\n */\n readonly maxBufferBytes?: number | null;\n\n /**\n * A Boolean value which determines whether the player should prioritize time over size when buffering media.\n *\n * @default false\n * @platform android\n */\n readonly prioritizeTimeOverSizeThreshold?: boolean;\n};\n\n/**\n * Specifies the content type of the source.\n *\n * - `auto`: The player will automatically determine the content type of the video.\n * - `progressive`: The player will use progressive download content type. This is the default `ContentType` when the uri does not contain an extension.\n * - `hls`: The player will use HLS content type.\n * - `dash`: The player will use DASH content type (Android-only).\n * - `smoothStreaming`: The player will use SmoothStreaming content type (Android-only).\n *\n * @default `auto`\n */\nexport type ContentType = 'auto' | 'progressive' | 'hls' | 'dash' | 'smoothStreaming';\n\n/**\n * Specifies the audio mode that the player should use. Audio mode is set on per-app basis, if there are multiple players playing and\n * have different a `AudioMode` specified, the highest priority mode will be used. Priority order: 'doNotMix' > 'auto' > 'duckOthers' > 'mixWithOthers'.\n *\n * - `mixWithOthers`: The player will mix its audio output with other apps.\n * - `duckOthers`: The player will lower the volume of other apps if any of the active players is outputting audio.\n * - `auto`: The player will allow other apps to keep playing audio only when it is muted. On iOS it will always interrupt other apps when `showNowPlayingNotification` is `true` due to system requirements.\n * - `doNotMix`: The player will pause playback in other apps, even when it's muted.\n *\n * > On iOS, the Now Playing notification is dependent on the audio mode. If the audio mode is different from `doNotMix` or `auto` this feature will not work.\n */\nexport type AudioMixingMode = 'mixWithOthers' | 'duckOthers' | 'auto' | 'doNotMix';\n\nexport type SubtitleTrack = {\n /**\n * A string used by `expo-video` to identify the subtitle track.\n *\n * @platform android\n */\n id?: string;\n\n /**\n * Language of the subtitle track. For example, `en`, `pl`, `de`.\n */\n language: string;\n\n /**\n * Label of the subtitle track in the language of the device.\n */\n label: string;\n\n /**\n * Name of the subtitle track as specified in the media source.\n * @platform android\n * @platform ios\n */\n name?: string;\n\n /**\n * Indicates whether this is the default subtitle track.\n * @platform android\n * @platform ios\n */\n isDefault?: boolean;\n\n /**\n * Indicates whether this track should be auto-selected based on user preferences.\n * @platform android\n * @platform ios\n */\n autoSelect?: boolean;\n};\n\n/**\n * Specifies a VideoTrack loaded from a [`VideoSource`](#videosource).\n */\nexport type VideoTrack = {\n /**\n * The id of the video track.\n *\n * > This field is platform-specific and may return different depending on the operating system.\n */\n id: string;\n\n /**\n * The URL of the `VideoTrack` for HLS video sources. `null` for other source types.\n */\n url: string | null;\n\n /**\n * Size of the video track.\n */\n size: VideoSize;\n\n /**\n * MimeType of the video track or null if unknown.\n */\n mimeType: string | null;\n\n /**\n * Indicates whether the video track format is supported by the device.\n *\n * @platform android\n */\n isSupported: boolean;\n\n /**\n * Specifies the bitrate in bits per second. This is the peak bitrate if known, or else the average bitrate if known, or else null.\n *\n * @deprecated Use `peakBitrate` or `averageBitrate` instead.\n */\n bitrate: number | null;\n\n /**\n * Specifies the average bitrate in bits per second or null if the value is unknown.\n *\n */\n averageBitrate: number | null;\n\n /**\n * Specifies the average bitrate in bits per second or null if the value is unknown.\n */\n peakBitrate: number | null;\n\n /**\n * Specifies the frame rate of the video track in frames per second.\n */\n frameRate: number | null;\n\n /**\n * Specifies the video range of the video track.\n */\n videoRange: VideoRange;\n};\n\n/**\n * Specifies the size of a video track.\n */\nexport type VideoSize = {\n /**\n * Width of the video track in pixels.\n */\n width: number;\n /**\n * Height of the video track in pixels.\n */\n height: number;\n};\n\nexport type AudioTrack = {\n /**\n * A string used by expo-video to identify the audio track.\n * @platform android\n */\n id?: string;\n\n /**\n * Language of the audio track. For example, 'en', 'pl', 'de'.\n */\n language: string;\n\n /**\n * Label of the audio track in the language of the device.\n */\n label: string;\n\n /**\n * Name of the audio track as specified in the media source.\n * @platform android\n * @platform ios\n */\n name?: string;\n\n /**\n * Indicates whether this is the default audio track.\n * @platform android\n * @platform ios\n */\n isDefault?: boolean;\n\n /**\n * Indicates whether this track should be auto-selected based on user preferences.\n * @platform android\n * @platform ios\n */\n autoSelect?: boolean;\n};\n\n/**\n * Determines the time that the actual position seeked to may precede or exceed the requested seek position.\n * Larger tolerance will usually result in faster seeking.\n * This property affects the precision of setting the [`currentTime`](#currenttime) property and the [`seekBy`](#seekbyseconds) method, and on Android, it also affects the accuracy of the scrubber from the default native controls.\n *\n * > If you are trying to optimize for scrubbing (many frequent seeks), also see [`ScrubbingModeOptions`](#scrubbingmodeoptions-1).\n *\n * @platform android\n * @platform ios\n */\nexport type SeekTolerance = {\n /**\n * The maximum time that the actual position seeked to may precede the requested seek position, in seconds. Must be non-negative.\n * @default 0\n */\n toleranceBefore?: number;\n\n /**\n * The maximum time that the actual position seeked to may exceed the requested seek position, in seconds. Must be non-negative.\n * @default 0\n */\n toleranceAfter?: number;\n};\n\n/**\n * Defines scrubbing mode options used by a [`VideoPlayer`](#videoplayer).\n */\nexport type ScrubbingModeOptions = {\n /**\n * Whether the codec operating rate should be increased in scrubbing mode.\n *\n * You should only enable this when the player is receiving a large number of seeks in a short period of time. For less frequent seeks, fine-tuning the [`SeekTolerance`](#seektolerance-1) may be sufficient.\n *\n * On Android, the player may consume more resources in this mode, so it should only be used for short periods of time in response to user interaction (for example, dragging on a progress bar UI element).\n *\n * On Android, when `scrubbingModeEnabled` is `true`, the playback is suppressed. You should set this property back to `false` when the user interaction ends to allow the playback to resume.\n * For best results, on iOS you should pause the playback when scrubbing.\n *\n * > For best scrubbing performance, consider also increasing the seeking tolerance using the [`SeekTolerance`](#seektolerance-1) property.\n *\n * > Other scrubbing mode options will have no effect when this is `false`.\n * @default false\n * @platform android\n * @platform ios\n */\n scrubbingModeEnabled?: boolean;\n\n /**\n * Whether the codec operating rate should be increased in scrubbing mode.\n *\n * @platform android\n * @default true\n */\n increaseCodecOperatingRate?: boolean;\n\n /**\n * Sets whether ExoPlayer's dynamic scheduling should be enabled in scrubbing mode.\n * This can result in available output buffers being handled more quickly when seeking.\n *\n * @platform android\n * @default true\n */\n enableDynamicScheduling?: boolean;\n /**\n * Sets whether to use `MediaCodec.BUFFER_FLAG_DECODE_ONLY` in scrubbing mode.\n * When playback is using MediaCodec on API 34+, this flag can speed up seeking by signalling that the decoded output of buffers between the previous keyframe and the target frame is not needed by the player.\n *\n * @platform android\n * @default true\n */\n useDecodeOnlyFlag?: boolean;\n\n /**\n * Sets whether to avoid flushing the decoder (where possible) in scrubbing mode.\n * When `true`, avoids flushing the decoder when a new seek starts decoding from a key-frame in compatible content.\n *\n * @platform android\n * @default true\n */\n allowSkippingMediaCodecFlush?: boolean;\n};\n\n/**\n * Options to apply to the player builder before the native constructor is invoked\n * @platform android\n */\nexport type PlayerBuilderOptions = {\n /**\n * Seek backward increment in seconds.\n * Values will be clamped between 0.001 and 999 seconds.\n * @platform android\n */\n seekBackwardIncrement?: number;\n\n /**\n * Seek forward increment in seconds.\n * Values will be clamped between 0.001 and 999 seconds.\n * @platform android\n */\n seekForwardIncrement?: number;\n};\n\n/**\n * Specifies the dynamic range of the video content.\n * - `sdr`: Standard Dynamic Range video.\n * - `hlg`: Hybrid Log-Gamma - HDR backward-compatible with SDR displays\n * - `pq`: Perceptual Quantizer - Formats like HDR10 and Dolby Vision\n */\nexport type VideoRange = 'sdr' | 'hlg' | 'pq';\n"]}
@@ -256,7 +256,14 @@ export type SubtitleStyle = {
256
256
  */
257
257
  edgeColor?: string;
258
258
  /**
259
- * The distance of the subtitles from the bottom of the video, expressed as a fraction of the view height (`0`–`1`).
259
+ * The distance of the subtitles from the bottom of the video, expressed as a fraction (`0`–`1`).
260
+ *
261
+ * The fraction is of the content frame, which with the default `contentFit="contain"` is the displayed
262
+ * picture rather than the whole view — so one value keeps captions at the same height relative to the
263
+ * image across screen sizes, pixel densities, orientations and letterboxed sources, with no conversion
264
+ * needed. With `contentFit="cover"` or `"fill"` the content frame is the view.
265
+ *
266
+ * @default 0.08
260
267
  * @platform android
261
268
  */
262
269
  bottomOffset?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"VideoView.types.d.ts","sourceRoot":"","sources":["../src/VideoView.types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"VideoView.types.d.ts","sourceRoot":"","sources":["../src/VideoView.types.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"VideoView.types.js","sourceRoot":"","sources":["../src/VideoView.types.ts"],"names":[],"mappings":"","sourcesContent":["import type { ViewProps } from 'react-native';\n\nimport type { VideoPlayer } from './VideoPlayer.types';\n\n/**\n * Describes how a video should be scaled to fit in a container.\n * - `contain`: The video maintains its aspect ratio and fits inside the container, with possible letterboxing/pillarboxing.\n * - `cover`: The video maintains its aspect ratio and covers the entire container, potentially cropping some portions.\n * - `fill`: The video stretches/squeezes to completely fill the container, potentially causing distortion.\n */\nexport type VideoContentFit = 'contain' | 'cover' | 'fill';\n\n/**\n * Describes the type of the surface used to render the video.\n * - `surfaceView`: Uses the `SurfaceView` to render the video. This value should be used in the majority of cases. Provides significantly lower power consumption, better performance, and more features.\n * - `textureView`: Uses the `TextureView` to render the video. Should be used in cases where the SurfaceView is not supported or causes issues (for example, overlapping video views).\n *\n * You can learn more about surface types in the official [ExoPlayer documentation](https://developer.android.com/media/media3/ui/playerview#surfacetype).\n * @platform android\n */\nexport type SurfaceType = 'textureView' | 'surfaceView';\n\nexport interface VideoViewProps extends ViewProps {\n /**\n * A video player instance. Use [`useVideoPlayer()`](#usevideoplayersource-setup) hook to create one.\n */\n player?: VideoPlayer | null;\n\n /**\n * Determines whether native controls should be displayed or not.\n *\n * > **Note**: Due to platform limitations, the native controls are always enabled in fullscreen mode.\n * @default true\n */\n nativeControls?: boolean;\n\n /**\n * Describes how the video should be scaled to fit in the container.\n * Options are `'contain'`, `'cover'`, and `'fill'`.\n * @default 'contain'\n */\n contentFit?: VideoContentFit;\n\n /**\n * Determines the fullscreen mode options.\n */\n fullscreenOptions?: FullscreenOptions;\n\n /**\n * Determines whether the timecodes should be displayed or not.\n * @default true\n * @platform ios\n */\n showsTimecodes?: boolean;\n\n /**\n * Determines whether the player allows the user to skip media content.\n * @default false\n * @platform android\n * @platform ios\n */\n requiresLinearPlayback?: boolean;\n\n /**\n * Configuration for controlling the visibility of player control buttons.\n * @platform android\n */\n buttonOptions?: ButtonOptions;\n\n /**\n * Customizes the appearance of the rendered subtitles/captions (text color, background, font, edge, and position).\n *\n * The style is applied to whatever subtitle track is currently displayed, regardless of whether it comes\n * from an embedded (`HLS`/`DASH`) track or an external one attached through\n * [`VideoSource.subtitleTracks`](#videosourcesubtitletracks).\n *\n * > **Note (iOS):** styling relies on `AVPlayerItem.textStyleRules`, which only affects `WebVTT` captions that\n * > the system renders itself. The OS-level *Settings → Accessibility → Subtitles & Captioning* preferences\n * > take precedence over these values when the user has enabled them.\n *\n * @platform android\n * @platform ios\n */\n subtitleStyle?: SubtitleStyle;\n\n /**\n * Determines the type of the surface used to render the video.\n * > This prop should not be changed at runtime.\n * @default 'surfaceView'\n * @platform android\n */\n surfaceType?: SurfaceType;\n\n /**\n * Determines the position offset of the video inside the container.\n * @default { dx: 0, dy: 0 }\n * @platform ios\n */\n contentPosition?: { dx?: number; dy?: number };\n\n /**\n * A callback to call after the video player enters Picture in Picture (PiP) mode.\n * @platform android\n * @platform ios\n * @platform web\n */\n onPictureInPictureStart?: () => void;\n\n /**\n * A callback to call after the video player exits Picture in Picture (PiP) mode.\n * @platform android\n * @platform ios\n * @platform web\n */\n onPictureInPictureStop?: () => void;\n\n /**\n * Determines whether the player allows Picture in Picture (PiP) mode.\n * > **Note:** The `supportsPictureInPicture` property of the [config plugin](#configuration-in-app-config)\n * > has to be configured for the PiP to work.\n * @platform android\n * @platform ios\n * @platform web\n */\n allowsPictureInPicture?: boolean;\n\n /**\n * Determines whether a video should be played \"inline\", that is, within the element's playback area.\n * @platform web\n */\n playsInline?: boolean;\n\n /**\n * Determines whether the player should start Picture in Picture (PiP) automatically when the app is in the background.\n * > **Note:** Only one player can be in Picture in Picture (PiP) mode at a time.\n *\n * > **Note:** The `supportsPictureInPicture` property of the [config plugin](#configuration-in-app-config)\n * > has to be configured for the PiP to work.\n *\n * @default false\n * @platform android 12+\n * @platform ios\n */\n startsPictureInPictureAutomatically?: boolean;\n\n /**\n * Specifies whether to perform video frame analysis (Live Text in videos).\n * Check official [Apple documentation](https://developer.apple.com/documentation/avkit/avplayerviewcontroller/allowsvideoframeanalysis) for more details.\n * @default true\n * @platform ios 16.0+\n */\n allowsVideoFrameAnalysis?: boolean;\n\n /**\n * A callback to call after the video player enters fullscreen mode.\n */\n onFullscreenEnter?: () => void;\n\n /**\n * A callback to call after the video player exits fullscreen mode.\n */\n onFullscreenExit?: () => void;\n\n /**\n * A callback to call after the mounted `VideoPlayer` has rendered the first frame into the `VideoView`.\n * This event can be used to hide any cover images that conceal the initial loading of the player.\n * > **Note:** This event may also be called during playback when the current video track changes (for example when the player switches video quality).\n */\n onFirstFrameRender?: () => void;\n\n /**\n * Determines whether the player should use the default ExoPlayer shutter that covers the `VideoView` before the first video frame is rendered.\n * Setting this property to `false` makes the Android behavior the same as iOS.\n *\n * @platform android\n * @default false\n */\n useExoShutter?: boolean;\n\n /**\n * Determines the [cross origin policy](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/crossorigin) used by the underlying native view on web.\n * If `undefined` (default), does not use CORS at all. If set to `'anonymous'`, the video will be loaded with CORS enabled.\n * Note that some videos may not play if CORS is enabled, depending on the CDN settings.\n * If you encounter issues, consider adjusting the `crossOrigin` property.\n *\n *\n * @platform web\n * @default undefined\n */\n crossOrigin?: 'anonymous' | 'use-credentials';\n\n /**\n * Use Audio Nodes for sound playback. When the same player is playing in multiple video views the audio won't increase in volume\n * as the number of players increases.\n *\n * > **Note**: This property is experimental, when enabled it is known to break audio for some sources. Do not change this property at runtime.\n *\n * @experimental\n * @default false\n * @platform web\n */\n useAudioNodePlayback?: boolean;\n}\n\n/**\n * Describes the edge (outline) effect drawn around subtitle text.\n * - `none`: No edge effect.\n * - `outline`: A solid outline around each glyph.\n * - `dropShadow`: A drop shadow behind the text.\n * - `raised`: The text appears raised from the background.\n * - `depressed`: The text appears pressed into the background.\n */\nexport type SubtitleEdgeType = 'none' | 'outline' | 'dropShadow' | 'raised' | 'depressed';\n\n/**\n * Describes the visual style applied to the rendered subtitles/captions.\n *\n * Colors accept a hex string in the form `#RGB`, `#RRGGBB`, or `#RRGGBBAA` (an alpha suffix controls opacity).\n *\n * @platform android\n * @platform ios\n */\nexport type SubtitleStyle = {\n /**\n * The color of the subtitle text.\n * @default '#FFFFFF'\n */\n textColor?: string;\n\n /**\n * The background color drawn directly behind the text glyphs (the caption \"box\").\n * Use an alpha suffix (for example `#000000A0`) for a translucent box.\n * @default 'transparent'\n */\n backgroundColor?: string;\n\n /**\n * The background color of the whole cue region (the \"window\" behind the whole line).\n * @default 'transparent'\n */\n windowColor?: string;\n\n /**\n * The subtitle font size.\n *\n * > The unit differs per platform: on Android this is an absolute size in `sp`; on iOS it is a percentage\n * > relative to the default caption size (`100` = default, `150` = 1.5×).\n *\n * @platform android\n * @platform ios\n */\n fontSize?: number;\n\n /**\n * The name of the font family used to render the subtitles.\n *\n * Custom fonts bundled with the app are supported, using the same name you would pass to a\n * `<Text>` component's `fontFamily`:\n * - On Android the font is looked up as an Android font resource (`res/font`, where `expo-font`'s\n * config plugin places fonts) and then through React Native's font manager, which covers the\n * `assets/fonts/<name>.ttf` convention and fonts registered at runtime.\n * - On iOS the font must be registered with the system, which is what bundling it through\n * `expo-font`'s config plugin (`UIAppFonts`) does.\n *\n * The built-in system families always work — for example `sans-serif`, `sans-serif-medium`,\n * `serif`, `monospace` and `cursive` on Android.\n *\n * > A name that matches no font falls back to the default typeface silently: neither platform\n * > reports an unresolved font family, so double-check the spelling if nothing changes.\n */\n fontFamily?: string;\n\n /**\n * Whether the subtitle text should be rendered in bold.\n * @default false\n */\n bold?: boolean;\n\n /**\n * The edge (outline) effect drawn around the text.\n * @default 'none'\n */\n edgeType?: SubtitleEdgeType;\n\n /**\n * The color of the edge effect. Only has an effect when `edgeType` is not `'none'`.\n */\n edgeColor?: string;\n\n /**\n * The distance of the subtitles from the bottom of the video, expressed as a fraction of the view height (`0`–`1`).\n * @platform android\n */\n bottomOffset?: number;\n\n /**\n * Corner radius, in dp, of the box drawn behind the caption text by [`backgroundColor`](#subtitlestylebackgroundcolor).\n *\n * A box is drawn per rendered line, so a caption that wraps onto two lines gets two rounded boxes, the\n * way VLC and most desktop players draw them. Has no effect unless `backgroundColor` is set and not\n * fully transparent.\n *\n * > **Note:** [`windowColor`](#subtitlestylewindowcolor) is not affected — the caption *window* stays\n * > rectangular. Rounding applies to the box that hugs the text.\n *\n * @default 0 (square corners)\n * @platform android\n */\n backgroundRadius?: number;\n\n /**\n * Whether the inline formatting carried by the subtitle track itself should be kept.\n *\n * Subtitle formats bring their own styling — `SubRip`'s `<b>`, `<i>`, `<u>` and `<font color>` tags, and the\n * richer per-cue styling of embedded `WebVTT`/`TTML` tracks. By default all of it is stripped, so the other\n * properties of this object are the only thing that decides how captions look. Set this to `true` to let a\n * track style its own text.\n *\n * The remaining `SubtitleStyle` properties still apply underneath, and `fontSize` keeps overriding any size\n * the track asks for. Only the properties a cue explicitly sets are affected — an unstyled cue looks the same\n * either way.\n *\n * > **Note:** a track that specifies its own colors will override [`textColor`](#subtitlestyletextcolor) and\n * > [`windowColor`](#subtitlestylewindowcolor) for the cues that specify them.\n *\n * @default false\n * @platform android\n */\n applyEmbeddedStyles?: boolean;\n};\n\n/**\n * Configuration for controlling the visibility of player control buttons.\n *\n * > The fullscreen button should be controlled with [`fullscreenOptions.enable`](#fullscreenoptions).\n *\n * @platform android\n */\nexport type ButtonOptions = {\n /**\n * Whether to show the next button.\n * @default false\n */\n showNext?: boolean;\n /**\n * Whether to show the previous button.\n * @default false\n */\n showPrevious?: boolean;\n /**\n * Whether to show the seek forward button.\n * @default true\n */\n showSeekForward?: boolean;\n /**\n * Whether to show the seek backward button.\n * @default true\n */\n showSeekBackward?: boolean;\n /**\n * Whether to show the subtitles button.\n * - `true`: Button is always visible\n * - `false`: Button is never visible\n * - `undefined`: Button is visible only when subtitles are available (default behavior)\n * @default undefined\n */\n showSubtitles?: boolean | null;\n /**\n * Whether to show the settings button.\n * @default true\n */\n showSettings?: boolean;\n /**\n * Whether to show the play/pause button.\n * @default true\n */\n showPlayPause?: boolean;\n /**\n * Whether to show the bottom control bar (containing time, progress bar, and buttons).\n * When set to `false`, the entire bottom bar including the progress bar will be hidden.\n *\n * > **Note**: The bottom bar is always visible in fullscreen mode to allow users to exit fullscreen.\n *\n * @default true\n */\n showBottomBar?: boolean;\n};\n\n/**\n * Describes the orientation of the video in fullscreen mode. Available values are:\n * - `default`: The video is displayed in any of the available device rotations.\n * - `portrait`: The video is displayed in one of two available portrait orientations and rotates between them.\n * - `portraitUp`: The video is displayed in the portrait orientation - the notch of the phone points upwards.\n * - `portraitDown`: The video is displayed in the portrait orientation - the notch of the phone points downwards.\n * - `landscape`: The video is displayed in one of two available landscape orientations and rotates between them.\n * - `landscapeLeft`: The video is displayed in the left landscape orientation - the notch of the phone is in the left palm of the user.\n * - `landscapeRight`: The video is displayed in the right landscape orientation - the notch of the phone is in the right palm of the user.\n */\nexport type FullscreenOrientation =\n | 'default'\n | 'portrait'\n | 'portraitUp'\n | 'portraitDown'\n | 'landscape'\n | 'landscapeLeft'\n | 'landscapeRight';\n\n/**\n * Determines whether the player keeps fullscreen when Picture in Picture (PiP) stops.\n * Only has an effect if the player was in fullscreen when PiP started.\n * - `'always'`: Always re-enter fullscreen when PiP stops.\n * - `'autoEnter'`: Re-enter fullscreen only when PiP was started automatically by the app going to the background.\n * - `'never'`: Do not re-enter fullscreen when PiP stops.\n */\nexport type KeepFullscreenOnPiPStopBehavior = 'always' | 'autoEnter' | 'never';\n\n/**\n * Describes the options for fullscreen video mode.\n */\nexport type FullscreenOptions = {\n /**\n * Specifies whether the fullscreen mode should be available to the user. When `false`, the fullscreen button will be hidden in the player.\n * @default true\n */\n enable: boolean;\n /**\n * Specifies the orientation of the video in fullscreen mode.\n * @default 'default'\n * @platform android\n * @platform ios\n */\n orientation?: FullscreenOrientation;\n /**\n * Specifies whether the app should exit fullscreen mode when the device is rotated to a different orientation than the one specified in the `orientation` prop.\n * For example, if the `orientation` prop is set to `landscape` and the device is rotated to `portrait`, the app will exit fullscreen mode.\n *\n * > This prop will have no effect if the `orientation` prop is set to `default`.\n * > The `VideoView` will never auto-exit fullscreen when the device auto-rotate feature has been disabled in settings.\n *\n * @default false\n * @platform android\n * @platform ios\n */\n autoExitOnRotate?: boolean;\n\n /**\n * Determines whether the player keeps fullscreen when Picture in Picture (PiP) stops.\n * Only has an effect if the player was in fullscreen when PiP started.\n *\n * @default 'autoEnter'\n * @platform ios\n */\n keepFullscreenOnPiPStop?: KeepFullscreenOnPiPStopBehavior;\n};\n"]}
1
+ {"version":3,"file":"VideoView.types.js","sourceRoot":"","sources":["../src/VideoView.types.ts"],"names":[],"mappings":"","sourcesContent":["import type { ViewProps } from 'react-native';\n\nimport type { VideoPlayer } from './VideoPlayer.types';\n\n/**\n * Describes how a video should be scaled to fit in a container.\n * - `contain`: The video maintains its aspect ratio and fits inside the container, with possible letterboxing/pillarboxing.\n * - `cover`: The video maintains its aspect ratio and covers the entire container, potentially cropping some portions.\n * - `fill`: The video stretches/squeezes to completely fill the container, potentially causing distortion.\n */\nexport type VideoContentFit = 'contain' | 'cover' | 'fill';\n\n/**\n * Describes the type of the surface used to render the video.\n * - `surfaceView`: Uses the `SurfaceView` to render the video. This value should be used in the majority of cases. Provides significantly lower power consumption, better performance, and more features.\n * - `textureView`: Uses the `TextureView` to render the video. Should be used in cases where the SurfaceView is not supported or causes issues (for example, overlapping video views).\n *\n * You can learn more about surface types in the official [ExoPlayer documentation](https://developer.android.com/media/media3/ui/playerview#surfacetype).\n * @platform android\n */\nexport type SurfaceType = 'textureView' | 'surfaceView';\n\nexport interface VideoViewProps extends ViewProps {\n /**\n * A video player instance. Use [`useVideoPlayer()`](#usevideoplayersource-setup) hook to create one.\n */\n player?: VideoPlayer | null;\n\n /**\n * Determines whether native controls should be displayed or not.\n *\n * > **Note**: Due to platform limitations, the native controls are always enabled in fullscreen mode.\n * @default true\n */\n nativeControls?: boolean;\n\n /**\n * Describes how the video should be scaled to fit in the container.\n * Options are `'contain'`, `'cover'`, and `'fill'`.\n * @default 'contain'\n */\n contentFit?: VideoContentFit;\n\n /**\n * Determines the fullscreen mode options.\n */\n fullscreenOptions?: FullscreenOptions;\n\n /**\n * Determines whether the timecodes should be displayed or not.\n * @default true\n * @platform ios\n */\n showsTimecodes?: boolean;\n\n /**\n * Determines whether the player allows the user to skip media content.\n * @default false\n * @platform android\n * @platform ios\n */\n requiresLinearPlayback?: boolean;\n\n /**\n * Configuration for controlling the visibility of player control buttons.\n * @platform android\n */\n buttonOptions?: ButtonOptions;\n\n /**\n * Customizes the appearance of the rendered subtitles/captions (text color, background, font, edge, and position).\n *\n * The style is applied to whatever subtitle track is currently displayed, regardless of whether it comes\n * from an embedded (`HLS`/`DASH`) track or an external one attached through\n * [`VideoSource.subtitleTracks`](#videosourcesubtitletracks).\n *\n * > **Note (iOS):** styling relies on `AVPlayerItem.textStyleRules`, which only affects `WebVTT` captions that\n * > the system renders itself. The OS-level *Settings → Accessibility → Subtitles & Captioning* preferences\n * > take precedence over these values when the user has enabled them.\n *\n * @platform android\n * @platform ios\n */\n subtitleStyle?: SubtitleStyle;\n\n /**\n * Determines the type of the surface used to render the video.\n * > This prop should not be changed at runtime.\n * @default 'surfaceView'\n * @platform android\n */\n surfaceType?: SurfaceType;\n\n /**\n * Determines the position offset of the video inside the container.\n * @default { dx: 0, dy: 0 }\n * @platform ios\n */\n contentPosition?: { dx?: number; dy?: number };\n\n /**\n * A callback to call after the video player enters Picture in Picture (PiP) mode.\n * @platform android\n * @platform ios\n * @platform web\n */\n onPictureInPictureStart?: () => void;\n\n /**\n * A callback to call after the video player exits Picture in Picture (PiP) mode.\n * @platform android\n * @platform ios\n * @platform web\n */\n onPictureInPictureStop?: () => void;\n\n /**\n * Determines whether the player allows Picture in Picture (PiP) mode.\n * > **Note:** The `supportsPictureInPicture` property of the [config plugin](#configuration-in-app-config)\n * > has to be configured for the PiP to work.\n * @platform android\n * @platform ios\n * @platform web\n */\n allowsPictureInPicture?: boolean;\n\n /**\n * Determines whether a video should be played \"inline\", that is, within the element's playback area.\n * @platform web\n */\n playsInline?: boolean;\n\n /**\n * Determines whether the player should start Picture in Picture (PiP) automatically when the app is in the background.\n * > **Note:** Only one player can be in Picture in Picture (PiP) mode at a time.\n *\n * > **Note:** The `supportsPictureInPicture` property of the [config plugin](#configuration-in-app-config)\n * > has to be configured for the PiP to work.\n *\n * @default false\n * @platform android 12+\n * @platform ios\n */\n startsPictureInPictureAutomatically?: boolean;\n\n /**\n * Specifies whether to perform video frame analysis (Live Text in videos).\n * Check official [Apple documentation](https://developer.apple.com/documentation/avkit/avplayerviewcontroller/allowsvideoframeanalysis) for more details.\n * @default true\n * @platform ios 16.0+\n */\n allowsVideoFrameAnalysis?: boolean;\n\n /**\n * A callback to call after the video player enters fullscreen mode.\n */\n onFullscreenEnter?: () => void;\n\n /**\n * A callback to call after the video player exits fullscreen mode.\n */\n onFullscreenExit?: () => void;\n\n /**\n * A callback to call after the mounted `VideoPlayer` has rendered the first frame into the `VideoView`.\n * This event can be used to hide any cover images that conceal the initial loading of the player.\n * > **Note:** This event may also be called during playback when the current video track changes (for example when the player switches video quality).\n */\n onFirstFrameRender?: () => void;\n\n /**\n * Determines whether the player should use the default ExoPlayer shutter that covers the `VideoView` before the first video frame is rendered.\n * Setting this property to `false` makes the Android behavior the same as iOS.\n *\n * @platform android\n * @default false\n */\n useExoShutter?: boolean;\n\n /**\n * Determines the [cross origin policy](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/crossorigin) used by the underlying native view on web.\n * If `undefined` (default), does not use CORS at all. If set to `'anonymous'`, the video will be loaded with CORS enabled.\n * Note that some videos may not play if CORS is enabled, depending on the CDN settings.\n * If you encounter issues, consider adjusting the `crossOrigin` property.\n *\n *\n * @platform web\n * @default undefined\n */\n crossOrigin?: 'anonymous' | 'use-credentials';\n\n /**\n * Use Audio Nodes for sound playback. When the same player is playing in multiple video views the audio won't increase in volume\n * as the number of players increases.\n *\n * > **Note**: This property is experimental, when enabled it is known to break audio for some sources. Do not change this property at runtime.\n *\n * @experimental\n * @default false\n * @platform web\n */\n useAudioNodePlayback?: boolean;\n}\n\n/**\n * Describes the edge (outline) effect drawn around subtitle text.\n * - `none`: No edge effect.\n * - `outline`: A solid outline around each glyph.\n * - `dropShadow`: A drop shadow behind the text.\n * - `raised`: The text appears raised from the background.\n * - `depressed`: The text appears pressed into the background.\n */\nexport type SubtitleEdgeType = 'none' | 'outline' | 'dropShadow' | 'raised' | 'depressed';\n\n/**\n * Describes the visual style applied to the rendered subtitles/captions.\n *\n * Colors accept a hex string in the form `#RGB`, `#RRGGBB`, or `#RRGGBBAA` (an alpha suffix controls opacity).\n *\n * @platform android\n * @platform ios\n */\nexport type SubtitleStyle = {\n /**\n * The color of the subtitle text.\n * @default '#FFFFFF'\n */\n textColor?: string;\n\n /**\n * The background color drawn directly behind the text glyphs (the caption \"box\").\n * Use an alpha suffix (for example `#000000A0`) for a translucent box.\n * @default 'transparent'\n */\n backgroundColor?: string;\n\n /**\n * The background color of the whole cue region (the \"window\" behind the whole line).\n * @default 'transparent'\n */\n windowColor?: string;\n\n /**\n * The subtitle font size.\n *\n * > The unit differs per platform: on Android this is an absolute size in `sp`; on iOS it is a percentage\n * > relative to the default caption size (`100` = default, `150` = 1.5×).\n *\n * @platform android\n * @platform ios\n */\n fontSize?: number;\n\n /**\n * The name of the font family used to render the subtitles.\n *\n * Custom fonts bundled with the app are supported, using the same name you would pass to a\n * `<Text>` component's `fontFamily`:\n * - On Android the font is looked up as an Android font resource (`res/font`, where `expo-font`'s\n * config plugin places fonts) and then through React Native's font manager, which covers the\n * `assets/fonts/<name>.ttf` convention and fonts registered at runtime.\n * - On iOS the font must be registered with the system, which is what bundling it through\n * `expo-font`'s config plugin (`UIAppFonts`) does.\n *\n * The built-in system families always work — for example `sans-serif`, `sans-serif-medium`,\n * `serif`, `monospace` and `cursive` on Android.\n *\n * > A name that matches no font falls back to the default typeface silently: neither platform\n * > reports an unresolved font family, so double-check the spelling if nothing changes.\n */\n fontFamily?: string;\n\n /**\n * Whether the subtitle text should be rendered in bold.\n * @default false\n */\n bold?: boolean;\n\n /**\n * The edge (outline) effect drawn around the text.\n * @default 'none'\n */\n edgeType?: SubtitleEdgeType;\n\n /**\n * The color of the edge effect. Only has an effect when `edgeType` is not `'none'`.\n */\n edgeColor?: string;\n\n /**\n * The distance of the subtitles from the bottom of the video, expressed as a fraction (`0`–`1`).\n *\n * The fraction is of the content frame, which with the default `contentFit=\"contain\"` is the displayed\n * picture rather than the whole view — so one value keeps captions at the same height relative to the\n * image across screen sizes, pixel densities, orientations and letterboxed sources, with no conversion\n * needed. With `contentFit=\"cover\"` or `\"fill\"` the content frame is the view.\n *\n * @default 0.08\n * @platform android\n */\n bottomOffset?: number;\n\n /**\n * Corner radius, in dp, of the box drawn behind the caption text by [`backgroundColor`](#subtitlestylebackgroundcolor).\n *\n * A box is drawn per rendered line, so a caption that wraps onto two lines gets two rounded boxes, the\n * way VLC and most desktop players draw them. Has no effect unless `backgroundColor` is set and not\n * fully transparent.\n *\n * > **Note:** [`windowColor`](#subtitlestylewindowcolor) is not affected — the caption *window* stays\n * > rectangular. Rounding applies to the box that hugs the text.\n *\n * @default 0 (square corners)\n * @platform android\n */\n backgroundRadius?: number;\n\n /**\n * Whether the inline formatting carried by the subtitle track itself should be kept.\n *\n * Subtitle formats bring their own styling — `SubRip`'s `<b>`, `<i>`, `<u>` and `<font color>` tags, and the\n * richer per-cue styling of embedded `WebVTT`/`TTML` tracks. By default all of it is stripped, so the other\n * properties of this object are the only thing that decides how captions look. Set this to `true` to let a\n * track style its own text.\n *\n * The remaining `SubtitleStyle` properties still apply underneath, and `fontSize` keeps overriding any size\n * the track asks for. Only the properties a cue explicitly sets are affected — an unstyled cue looks the same\n * either way.\n *\n * > **Note:** a track that specifies its own colors will override [`textColor`](#subtitlestyletextcolor) and\n * > [`windowColor`](#subtitlestylewindowcolor) for the cues that specify them.\n *\n * @default false\n * @platform android\n */\n applyEmbeddedStyles?: boolean;\n};\n\n/**\n * Configuration for controlling the visibility of player control buttons.\n *\n * > The fullscreen button should be controlled with [`fullscreenOptions.enable`](#fullscreenoptions).\n *\n * @platform android\n */\nexport type ButtonOptions = {\n /**\n * Whether to show the next button.\n * @default false\n */\n showNext?: boolean;\n /**\n * Whether to show the previous button.\n * @default false\n */\n showPrevious?: boolean;\n /**\n * Whether to show the seek forward button.\n * @default true\n */\n showSeekForward?: boolean;\n /**\n * Whether to show the seek backward button.\n * @default true\n */\n showSeekBackward?: boolean;\n /**\n * Whether to show the subtitles button.\n * - `true`: Button is always visible\n * - `false`: Button is never visible\n * - `undefined`: Button is visible only when subtitles are available (default behavior)\n * @default undefined\n */\n showSubtitles?: boolean | null;\n /**\n * Whether to show the settings button.\n * @default true\n */\n showSettings?: boolean;\n /**\n * Whether to show the play/pause button.\n * @default true\n */\n showPlayPause?: boolean;\n /**\n * Whether to show the bottom control bar (containing time, progress bar, and buttons).\n * When set to `false`, the entire bottom bar including the progress bar will be hidden.\n *\n * > **Note**: The bottom bar is always visible in fullscreen mode to allow users to exit fullscreen.\n *\n * @default true\n */\n showBottomBar?: boolean;\n};\n\n/**\n * Describes the orientation of the video in fullscreen mode. Available values are:\n * - `default`: The video is displayed in any of the available device rotations.\n * - `portrait`: The video is displayed in one of two available portrait orientations and rotates between them.\n * - `portraitUp`: The video is displayed in the portrait orientation - the notch of the phone points upwards.\n * - `portraitDown`: The video is displayed in the portrait orientation - the notch of the phone points downwards.\n * - `landscape`: The video is displayed in one of two available landscape orientations and rotates between them.\n * - `landscapeLeft`: The video is displayed in the left landscape orientation - the notch of the phone is in the left palm of the user.\n * - `landscapeRight`: The video is displayed in the right landscape orientation - the notch of the phone is in the right palm of the user.\n */\nexport type FullscreenOrientation =\n | 'default'\n | 'portrait'\n | 'portraitUp'\n | 'portraitDown'\n | 'landscape'\n | 'landscapeLeft'\n | 'landscapeRight';\n\n/**\n * Determines whether the player keeps fullscreen when Picture in Picture (PiP) stops.\n * Only has an effect if the player was in fullscreen when PiP started.\n * - `'always'`: Always re-enter fullscreen when PiP stops.\n * - `'autoEnter'`: Re-enter fullscreen only when PiP was started automatically by the app going to the background.\n * - `'never'`: Do not re-enter fullscreen when PiP stops.\n */\nexport type KeepFullscreenOnPiPStopBehavior = 'always' | 'autoEnter' | 'never';\n\n/**\n * Describes the options for fullscreen video mode.\n */\nexport type FullscreenOptions = {\n /**\n * Specifies whether the fullscreen mode should be available to the user. When `false`, the fullscreen button will be hidden in the player.\n * @default true\n */\n enable: boolean;\n /**\n * Specifies the orientation of the video in fullscreen mode.\n * @default 'default'\n * @platform android\n * @platform ios\n */\n orientation?: FullscreenOrientation;\n /**\n * Specifies whether the app should exit fullscreen mode when the device is rotated to a different orientation than the one specified in the `orientation` prop.\n * For example, if the `orientation` prop is set to `landscape` and the device is rotated to `portrait`, the app will exit fullscreen mode.\n *\n * > This prop will have no effect if the `orientation` prop is set to `default`.\n * > The `VideoView` will never auto-exit fullscreen when the device auto-rotate feature has been disabled in settings.\n *\n * @default false\n * @platform android\n * @platform ios\n */\n autoExitOnRotate?: boolean;\n\n /**\n * Determines whether the player keeps fullscreen when Picture in Picture (PiP) stops.\n * Only has an effect if the player was in fullscreen when PiP started.\n *\n * @default 'autoEnter'\n * @platform ios\n */\n keepFullscreenOnPiPStop?: KeepFullscreenOnPiPStopBehavior;\n};\n"]}
@@ -21,6 +21,15 @@ internal struct SubtitleSource: Record {
21
21
  @Field
22
22
  var `default`: Bool = false
23
23
 
24
+ /// How far above the bottom of the video this track renders, as a fraction of the video height.
25
+ ///
26
+ /// Applied by rewriting the WebVTT with a `line:` cue setting, because it has to be baked in when the
27
+ /// composition is built — see `VideoPlayerSubtitleSideload`. It lives on the source rather than on
28
+ /// `SubtitleStyle` for that reason: the composition is built from a `VideoSource` alone, before any
29
+ /// `VideoView` has handed the player a style.
30
+ @Field
31
+ var bottomOffset: Double? = nil
32
+
24
33
  /// Whether the source is a SubRip (`.srt`) file, which must be converted to WebVTT before sideloading.
25
34
  var isSrt: Bool {
26
35
  if let format {
@@ -107,8 +107,9 @@ internal enum VideoPlayerSubtitleSideload {
107
107
  return nil
108
108
  }
109
109
 
110
- // Fast path: a local WebVTT file can be used as-is.
111
- if uri.isFileURL && !subtitle.isSrt {
110
+ // Fast path: a local WebVTT file can be used as-is — unless it has to be rewritten to carry a
111
+ // line position.
112
+ if uri.isFileURL && !subtitle.isSrt && subtitle.bottomOffset == nil {
112
113
  return uri
113
114
  }
114
115
 
@@ -123,14 +124,19 @@ internal enum VideoPlayerSubtitleSideload {
123
124
  return nil
124
125
  }
125
126
 
126
- if subtitle.isSrt {
127
- guard
128
- let srtString = String(data: data, encoding: .utf8),
129
- let vttData = SubtitleConverter.srtToVtt(srtString).data(using: .utf8)
130
- else {
127
+ // Decode only when there is something to rewrite, so an untouched WebVTT keeps its original bytes
128
+ // (and its original encoding) instead of being round-tripped through UTF-8.
129
+ if subtitle.isSrt || subtitle.bottomOffset != nil {
130
+ guard var text = String(data: data, encoding: .utf8) else {
131
131
  return nil
132
132
  }
133
- data = vttData
133
+ if subtitle.isSrt {
134
+ text = SubtitleConverter.srtToVtt(text)
135
+ }
136
+ if let bottomOffset = subtitle.bottomOffset {
137
+ text = SubtitleConverter.applyLinePosition(text, bottomOffset: bottomOffset)
138
+ }
139
+ data = Data(text.utf8)
134
140
  }
135
141
 
136
142
  return writeTempVtt(data: data, source: subtitle)
@@ -160,7 +166,26 @@ internal enum VideoPlayerSubtitleSideload {
160
166
 
161
167
  // Derive a process-stable filename (SHA-256) so identical subtitles always resolve to the same file.
162
168
  // Swift's `Data.hashValue` is randomly seeded per launch, which would leak a new temp file every run.
163
- let identifier = "\(source.uri?.absoluteString ?? "")-\(source.language ?? "")"
169
+ //
170
+ // `bottomOffset` is part of the identity because it changes the bytes written: without it, the
171
+ // reuse check below would hand back a rendition positioned for a previous offset.
172
+ //
173
+ // `formatVersion` covers the same hazard across an *upgrade*: the inputs can be identical while
174
+ // the bytes this version writes are not, and the reuse check would then serve the file the
175
+ // previous version wrote — on exactly the devices being used to test the change. Bump it whenever
176
+ // the written form changes. v2: `applyLinePosition` gained the `,end` line alignment.
177
+ //
178
+ // Joined on NUL rather than a hyphen because a hyphen occurs in both of the variable parts — BCP-47
179
+ // tags carry one (`pt-BR`) and so does almost every URL — which lets two different subtitles
180
+ // flatten to the same string and collide on the hash. NUL cannot appear in either.
181
+ let formatVersion = "v2"
182
+ let offsetKey = source.bottomOffset.map { String(format: "%.4f", $0) } ?? ""
183
+ let identifier = [
184
+ formatVersion,
185
+ source.uri?.absoluteString ?? "",
186
+ source.language ?? "",
187
+ offsetKey
188
+ ].joined(separator: "\u{0}")
164
189
  let digest = SHA256.hash(data: Data(identifier.utf8))
165
190
  let hashString = digest.compactMap { String(format: "%02x", $0) }.joined()
166
191
  let fileURL = directory.appendingPathComponent("\(hashString).vtt")
@@ -183,6 +208,11 @@ internal enum VideoPlayerSubtitleSideload {
183
208
 
184
209
  /// Minimal SubRip (`.srt`) → WebVTT (`.vtt`) converter. AVFoundation only reads WebVTT as a `.text` track.
185
210
  internal enum SubtitleConverter {
211
+ /// A WebVTT cue timing line, anchored at the start: `[HH:]MM:SS.mmm --> [HH:]MM:SS.mmm`, optionally
212
+ /// followed by cue settings. Hours are optional per the WebVTT grammar.
213
+ private static let cueTimingPattern =
214
+ #"^[ \t]*(?:\d{2,}:)?[0-5]\d:[0-5]\d\.\d{3}[ \t]+-->[ \t]+(?:\d{2,}:)?[0-5]\d:[0-5]\d\.\d{3}"#
215
+
186
216
  static func srtToVtt(_ srt: String) -> String {
187
217
  let normalized = srt
188
218
  .replacingOccurrences(of: "\r\n", with: "\n")
@@ -200,4 +230,52 @@ internal enum SubtitleConverter {
200
230
  }
201
231
  return "WEBVTT\n\n" + converted
202
232
  }
233
+
234
+ /// Adds a WebVTT `line:` cue setting to every cue that does not already position itself.
235
+ ///
236
+ /// This is how a subtitle is moved clear of burned-in captions on iOS. AVFoundation renders legible
237
+ /// tracks itself and `AVPlayerItem.textStyleRules` carries no positioning, so the position has to
238
+ /// travel inside the WebVTT — which is also why it is fixed when the composition is built.
239
+ ///
240
+ /// - Parameter bottomOffset: fraction of the video height to sit above the bottom, matching the
241
+ /// Android `subtitleStyle.bottomOffset`. WebVTT counts `line` down from the top, so it is inverted.
242
+ ///
243
+ /// The emitted setting is `line:<percentage>,end`. The trailing `,end` is the WebVTT *line alignment*
244
+ /// component — not a separate `line-align` setting, which is not one of the six cue setting names —
245
+ /// and it is what makes the percentage refer to the *bottom* edge of the cue box, matching Android's
246
+ /// `setBottomPaddingFraction`. A bare percentage anchors the *top*, so a cue wrapping onto two lines
247
+ /// hangs a full line lower on iOS than on Android; at typical caption sizes that is 5–13% of the video
248
+ /// height, comparable to the offset itself, and enough to put the second line back over the burned-in
249
+ /// captions this exists to escape. It also fixes `bottomOffset: 0`, which as a bare `line:100%` put the
250
+ /// top of the box at the bottom edge of the video and hid the subtitle entirely.
251
+ ///
252
+ /// The alignment component is a known risk: a parser that predates it does not merely ignore it, it
253
+ /// discards the whole `line` setting, which would leave captions unpositioned rather than
254
+ /// top-anchored. There is no form that degrades gracefully — the two behaviours are expressed by the
255
+ /// same setting — so whether AVFoundation honours it has to be confirmed on a device. See FORK.md §5.
256
+ static func applyLinePosition(_ vtt: String, bottomOffset: Double) -> String {
257
+ let clamped = min(max(bottomOffset, 0), 1)
258
+ let linePercentage = Int(((1 - clamped) * 100).rounded())
259
+
260
+ let normalized = vtt
261
+ .replacingOccurrences(of: "\r\n", with: "\n")
262
+ .replacingOccurrences(of: "\r", with: "\n")
263
+
264
+ let positioned = normalized.components(separatedBy: "\n").map { line -> String in
265
+ // Only cue timing lines carry cue settings. Matched against the timestamp syntax rather than by
266
+ // looking for the arrow, because subtitle *text* is free to contain "-->" — dialogue and lyrics
267
+ // do — and appending a cue setting to it would print the setting on screen.
268
+ guard line.range(of: Self.cueTimingPattern, options: .regularExpression) != nil else {
269
+ return line
270
+ }
271
+ // A cue that already positions itself was written that way deliberately — left alone, matching
272
+ // how the Android side passes explicitly positioned cues through untouched.
273
+ guard !line.contains("line:") else {
274
+ return line
275
+ }
276
+ return "\(line) line:\(linePercentage)%,end"
277
+ }
278
+
279
+ return positioned.joined(separator: "\n")
280
+ }
203
281
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-video-subtitle",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "A cross-platform, performant video component for React Native and Expo with external subtitle sideloading and custom subtitle styling.",
5
5
  "keywords": [
6
6
  "expo",
@@ -459,6 +459,26 @@ export type SubtitleSource = {
459
459
  * @default false
460
460
  */
461
461
  default?: boolean;
462
+
463
+ /**
464
+ * How far above the bottom of the video this track renders, as a fraction of the video height (0–1).
465
+ *
466
+ * Useful when a video already carries burned-in (hardcoded) subtitles and the sideloaded ones would
467
+ * otherwise be drawn on top of them.
468
+ *
469
+ * It sits here rather than on [`subtitleStyle`](#videoviewprops) because iOS can only apply it while
470
+ * the subtitle is being merged into the video, which happens from the `VideoSource` alone — before any
471
+ * `VideoView` has supplied a style. Changing it therefore takes effect the next time the source loads.
472
+ *
473
+ * > **Note:** cues that position themselves are left alone, so this does nothing for a WebVTT track
474
+ * > whose cues already carry a `line:` setting.
475
+ *
476
+ * On **Android** use [`subtitleStyle.bottomOffset`](#subtitlestylebottomoffset) instead, which does the
477
+ * same thing for every track at once and updates live.
478
+ *
479
+ * @platform ios
480
+ */
481
+ bottomOffset?: number;
462
482
  };
463
483
 
464
484
  /**
@@ -288,7 +288,14 @@ export type SubtitleStyle = {
288
288
  edgeColor?: string;
289
289
 
290
290
  /**
291
- * The distance of the subtitles from the bottom of the video, expressed as a fraction of the view height (`0`–`1`).
291
+ * The distance of the subtitles from the bottom of the video, expressed as a fraction (`0`–`1`).
292
+ *
293
+ * The fraction is of the content frame, which with the default `contentFit="contain"` is the displayed
294
+ * picture rather than the whole view — so one value keeps captions at the same height relative to the
295
+ * image across screen sizes, pixel densities, orientations and letterboxed sources, with no conversion
296
+ * needed. With `contentFit="cover"` or `"fill"` the content frame is the view.
297
+ *
298
+ * @default 0.08
292
299
  * @platform android
293
300
  */
294
301
  bottomOffset?: number;