expo-video-subtitle 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. package/.gitattributes +17 -0
  2. package/CHANGELOG.md +120 -70
  3. package/FORK.md +116 -15
  4. package/LICENSE +21 -21
  5. package/README.md +186 -134
  6. package/android/build.gradle +32 -32
  7. package/android/src/main/AndroidManifest.xml +10 -10
  8. package/android/src/main/java/expo/modules/video/FullscreenPlayerActivity.kt +258 -258
  9. package/android/src/main/java/expo/modules/video/IntervalUpdateClock.kt +54 -54
  10. package/android/src/main/java/expo/modules/video/MediaMetadataRetriever.kt +89 -89
  11. package/android/src/main/java/expo/modules/video/PlayerViewExtension.kt +87 -87
  12. package/android/src/main/java/expo/modules/video/VideoCache.kt +109 -109
  13. package/android/src/main/java/expo/modules/video/VideoExceptions.kt +45 -45
  14. package/android/src/main/java/expo/modules/video/VideoModule.kt +446 -446
  15. package/android/src/main/java/expo/modules/video/VideoThumbnail.kt +20 -20
  16. package/android/src/main/java/expo/modules/video/VideoView.kt +457 -457
  17. package/android/src/main/java/expo/modules/video/delegates/IgnoreSameSet.kt +24 -24
  18. package/android/src/main/java/expo/modules/video/drawing/OutlineProvider.kt +217 -217
  19. package/android/src/main/java/expo/modules/video/enums/AudioMixingMode.kt +20 -20
  20. package/android/src/main/java/expo/modules/video/enums/ContentFit.kt +19 -19
  21. package/android/src/main/java/expo/modules/video/enums/ContentType.kt +22 -22
  22. package/android/src/main/java/expo/modules/video/enums/DRMType.kt +26 -26
  23. package/android/src/main/java/expo/modules/video/enums/FullscreenOrientation.kt +25 -25
  24. package/android/src/main/java/expo/modules/video/enums/PlayerStatus.kt +10 -10
  25. package/android/src/main/java/expo/modules/video/enums/VideoRange.kt +27 -27
  26. package/android/src/main/java/expo/modules/video/listeners/VideoManagerListener.kt +10 -10
  27. package/android/src/main/java/expo/modules/video/listeners/VideoPlayerListener.kt +35 -35
  28. package/android/src/main/java/expo/modules/video/listeners/VideoViewListener.kt +13 -13
  29. package/android/src/main/java/expo/modules/video/managers/AudioFocusManager.kt +242 -242
  30. package/android/src/main/java/expo/modules/video/managers/PictureInPictureManager.kt +329 -329
  31. package/android/src/main/java/expo/modules/video/managers/VideoManager.kt +194 -194
  32. package/android/src/main/java/expo/modules/video/playbackService/ExpoVideoPlaybackService.kt +267 -267
  33. package/android/src/main/java/expo/modules/video/playbackService/PlaybackServiceConnection.kt +62 -62
  34. package/android/src/main/java/expo/modules/video/playbackService/VideoMediaSessionCallback.kt +47 -47
  35. package/android/src/main/java/expo/modules/video/player/DefaultLoadControl.java +551 -551
  36. package/android/src/main/java/expo/modules/video/player/FirstFrameEventGenerator.kt +116 -116
  37. package/android/src/main/java/expo/modules/video/player/PlayerEvent.kt +172 -172
  38. package/android/src/main/java/expo/modules/video/player/VideoPlayer.kt +598 -598
  39. package/android/src/main/java/expo/modules/video/player/VideoPlayerAudioTracks.kt +126 -126
  40. package/android/src/main/java/expo/modules/video/player/VideoPlayerKeepAwake.kt +91 -91
  41. package/android/src/main/java/expo/modules/video/player/VideoPlayerLoadControl.kt +57 -57
  42. package/android/src/main/java/expo/modules/video/player/VideoPlayerSubtitles.kt +136 -136
  43. package/android/src/main/java/expo/modules/video/records/BufferOptions.kt +18 -18
  44. package/android/src/main/java/expo/modules/video/records/ButtonOptions.kt +17 -17
  45. package/android/src/main/java/expo/modules/video/records/DRMOptions.kt +27 -27
  46. package/android/src/main/java/expo/modules/video/records/FullscreenOptions.kt +14 -14
  47. package/android/src/main/java/expo/modules/video/records/PiPParams.kt +38 -38
  48. package/android/src/main/java/expo/modules/video/records/PlaybackError.kt +21 -21
  49. package/android/src/main/java/expo/modules/video/records/PlayerBuilderOptions.kt +15 -15
  50. package/android/src/main/java/expo/modules/video/records/ScrubbingModeOptions.kt +33 -33
  51. package/android/src/main/java/expo/modules/video/records/SeekTolerance.kt +30 -30
  52. package/android/src/main/java/expo/modules/video/records/SubtitleSource.kt +54 -54
  53. package/android/src/main/java/expo/modules/video/records/SubtitleStyle.kt +137 -131
  54. package/android/src/main/java/expo/modules/video/records/Tracks.kt +115 -115
  55. package/android/src/main/java/expo/modules/video/records/VideoEventPayloads.kt +79 -79
  56. package/android/src/main/java/expo/modules/video/records/VideoMetadata.kt +14 -14
  57. package/android/src/main/java/expo/modules/video/records/VideoSize.kt +16 -16
  58. package/android/src/main/java/expo/modules/video/records/VideoSource.kt +113 -113
  59. package/android/src/main/java/expo/modules/video/records/VideoThumbnailOptions.kt +26 -26
  60. package/android/src/main/java/expo/modules/video/utils/DataSourceUtils.kt +81 -81
  61. package/android/src/main/java/expo/modules/video/utils/EventDispatcherUtils.kt +43 -43
  62. package/android/src/main/java/expo/modules/video/utils/FullscreenActivityOrientationHelper.kt +120 -120
  63. package/android/src/main/java/expo/modules/video/utils/MediaSessionUtils.kt +14 -14
  64. package/android/src/main/java/expo/modules/video/utils/MutableWeakReference.kt +26 -26
  65. package/android/src/main/java/expo/modules/video/utils/PictureInPictureHelperFragment.kt +49 -49
  66. package/android/src/main/java/expo/modules/video/utils/PictureInPictureUtils.kt +96 -96
  67. package/android/src/main/java/expo/modules/video/utils/StackingSubtitleParser.kt +110 -24
  68. package/android/src/main/java/expo/modules/video/utils/SubtitleUtils.kt +78 -73
  69. package/android/src/main/java/expo/modules/video/utils/ViewVisibilityUtils.kt +28 -28
  70. package/android/src/main/java/expo/modules/video/utils/WeakMutableSet.kt +120 -120
  71. package/android/src/main/java/expo/modules/video/utils/YogaUtils.kt +20 -20
  72. package/android/src/main/res/drawable/seek_backwards_10s.xml +25 -25
  73. package/android/src/main/res/drawable/seek_backwards_15s.xml +25 -25
  74. package/android/src/main/res/drawable/seek_backwards_5s.xml +25 -25
  75. package/android/src/main/res/drawable/seek_forwards_10s.xml +30 -30
  76. package/android/src/main/res/drawable/seek_forwards_15s.xml +31 -31
  77. package/android/src/main/res/drawable/seek_forwards_5s.xml +30 -30
  78. package/android/src/main/res/layout/fullscreen_player_activity.xml +20 -20
  79. package/android/src/main/res/layout/surface_player_view.xml +7 -7
  80. package/android/src/main/res/layout/texture_player_view.xml +7 -7
  81. package/android/src/main/res/values/styles.xml +9 -9
  82. package/app.plugin.js +1 -1
  83. package/build/NativeVideoAirPlayButtonView.js.map +1 -1
  84. package/build/NativeVideoModule.js.map +1 -1
  85. package/build/NativeVideoModule.web.js.map +1 -1
  86. package/build/NativeVideoView.js.map +1 -1
  87. package/build/VideoAirPlayButton.ios.js.map +1 -1
  88. package/build/VideoAirPlayButton.js.map +1 -1
  89. package/build/VideoAirPlayButton.types.js.map +1 -1
  90. package/build/VideoModule.js.map +1 -1
  91. package/build/VideoPlayer.js.map +1 -1
  92. package/build/VideoPlayer.types.js.map +1 -1
  93. package/build/VideoPlayer.web.d.ts.map +1 -1
  94. package/build/VideoPlayer.web.js +5 -1
  95. package/build/VideoPlayer.web.js.map +1 -1
  96. package/build/VideoPlayerEvents.types.js.map +1 -1
  97. package/build/VideoThumbnail.js.map +1 -1
  98. package/build/VideoView.js.map +1 -1
  99. package/build/VideoView.types.d.ts +19 -0
  100. package/build/VideoView.types.d.ts.map +1 -1
  101. package/build/VideoView.types.js.map +1 -1
  102. package/build/VideoView.web.js.map +1 -1
  103. package/build/index.js.map +1 -1
  104. package/build/resolveAssetSource.js.map +1 -1
  105. package/build/resolveAssetSource.web.js.map +1 -1
  106. package/eslint.config.js +4 -0
  107. package/expo-module.config.json +9 -9
  108. package/ios/Cache/CachableRequest.swift +46 -46
  109. package/ios/Cache/CacheVideoAssetTransportProvider.swift +64 -64
  110. package/ios/Cache/CachedResource.swift +164 -164
  111. package/ios/Cache/CachingHelpers.swift +107 -107
  112. package/ios/Cache/MediaFileHandle.swift +139 -139
  113. package/ios/Cache/MediaInfo.swift +185 -185
  114. package/ios/Cache/ResourceLoaderDelegate.swift +306 -306
  115. package/ios/Cache/SynchronizedHashTable.swift +23 -23
  116. package/ios/Cache/VideoCacheManager.swift +192 -192
  117. package/ios/ContentKeyDelegate.swift +214 -214
  118. package/ios/ContentKeyManager.swift +21 -21
  119. package/ios/Enums/AudioMixingMode.swift +37 -37
  120. package/ios/Enums/ContentType.swift +11 -11
  121. package/ios/Enums/DRMType.swift +20 -20
  122. package/ios/Enums/FullscreenOrientation.swift +34 -34
  123. package/ios/Enums/KeepFullscreenOnPiPStopBehavior.swift +20 -20
  124. package/ios/Enums/PlayerStatus.swift +10 -10
  125. package/ios/Enums/VideoContentFit.swift +39 -39
  126. package/ios/Enums/VideoRange.swift +21 -21
  127. package/ios/ExpoVideo.podspec +27 -27
  128. package/ios/NowPlayingManager.swift +304 -304
  129. package/ios/OrientationAVPlayerViewController.swift +263 -263
  130. package/ios/Records/BufferOptions.swift +12 -12
  131. package/ios/Records/DRMOptions.swift +24 -24
  132. package/ios/Records/FullscreenOptions.swift +14 -14
  133. package/ios/Records/PlaybackError.swift +10 -10
  134. package/ios/Records/ScrubbingModeOptions.swift +7 -7
  135. package/ios/Records/SeekTolerance.swift +20 -20
  136. package/ios/Records/SubtitleSource.swift +32 -32
  137. package/ios/Records/SubtitleStyle.swift +118 -118
  138. package/ios/Records/Tracks.swift +229 -229
  139. package/ios/Records/VideoEventPayloads.swift +81 -81
  140. package/ios/Records/VideoMetadata.swift +16 -16
  141. package/ios/Records/VideoSize.swift +15 -15
  142. package/ios/Records/VideoSource.swift +28 -28
  143. package/ios/Thumbnails/VideoThumbnail.swift +27 -27
  144. package/ios/Thumbnails/VideoThumbnailGenerator.swift +68 -68
  145. package/ios/Thumbnails/VideoThumbnailOptions.swift +15 -15
  146. package/ios/Utils/AVAssetVariant+VideoTracks.swift +20 -20
  147. package/ios/Utils/AvAssetTrack+VideoTracks.swift +79 -79
  148. package/ios/Utils/FourCharCode+toString.swift +25 -25
  149. package/ios/Utils/HlsUriUtils.swift +27 -27
  150. package/ios/Utils/URL+MediaLibraryAssets.swift +68 -68
  151. package/ios/VideoAirPlayButtonView.swift +70 -70
  152. package/ios/VideoAsset.swift +98 -98
  153. package/ios/VideoAssetTransport/VideoAssetLoadPlan.swift +69 -69
  154. package/ios/VideoAssetTransport/VideoAssetSourceDescriptor.swift +29 -29
  155. package/ios/VideoAssetTransport/VideoAssetTransportProvider.swift +24 -24
  156. package/ios/VideoAssetTransport/VideoAssetTransportRegistry.swift +103 -103
  157. package/ios/VideoExceptions.swift +53 -53
  158. package/ios/VideoItem.swift +11 -11
  159. package/ios/VideoManager.swift +182 -182
  160. package/ios/VideoModule.swift +413 -413
  161. package/ios/VideoPlayer/DangerousPropertiesStore.swift +19 -19
  162. package/ios/VideoPlayer.swift +485 -485
  163. package/ios/VideoPlayerAudioTracks.swift +109 -109
  164. package/ios/VideoPlayerItem.swift +169 -169
  165. package/ios/VideoPlayerObserver.swift +612 -612
  166. package/ios/VideoPlayerSeeker.swift +77 -77
  167. package/ios/VideoPlayerSubtitleSideload.swift +203 -203
  168. package/ios/VideoPlayerSubtitles.swift +88 -88
  169. package/ios/VideoSourceLoader.swift +90 -90
  170. package/ios/VideoSourceLoaderListener.swift +34 -34
  171. package/ios/VideoView.swift +201 -201
  172. package/package.json +51 -51
  173. package/plugin/index.d.ts +1 -1
  174. package/plugin/index.js +1 -1
  175. package/plugin/jest.config.js +1 -1
  176. package/plugin/src/index.ts +3 -3
  177. package/plugin/src/withExpoVideo.ts +103 -103
  178. package/plugin/tsconfig.json +10 -10
  179. package/src/NativeVideoAirPlayButtonView.ts +3 -3
  180. package/src/NativeVideoModule.ts +16 -16
  181. package/src/NativeVideoModule.web.ts +4 -4
  182. package/src/NativeVideoView.ts +8 -8
  183. package/src/VideoAirPlayButton.ios.tsx +8 -8
  184. package/src/VideoAirPlayButton.tsx +14 -14
  185. package/src/VideoAirPlayButton.types.ts +39 -39
  186. package/src/VideoModule.ts +47 -47
  187. package/src/VideoPlayer.tsx +74 -74
  188. package/src/VideoPlayer.types.ts +893 -893
  189. package/src/VideoPlayer.web.tsx +470 -466
  190. package/src/VideoPlayerEvents.types.ts +332 -332
  191. package/src/VideoThumbnail.ts +31 -31
  192. package/src/VideoView.tsx +111 -111
  193. package/src/VideoView.types.ts +439 -419
  194. package/src/VideoView.web.tsx +293 -293
  195. package/src/index.ts +26 -26
  196. package/src/resolveAssetSource.ts +2 -2
  197. package/src/resolveAssetSource.web.ts +17 -17
  198. package/src/ts-declarations/react-native-assets.d.ts +22 -22
  199. package/tsconfig.all.json +11 -11
  200. package/tsconfig.json +10 -10
package/.gitattributes ADDED
@@ -0,0 +1,17 @@
1
+ # Keep the working copy at LF on every platform. Git already stores these files with LF, but a
2
+ # Windows checkout with core.autocrlf=true rewrites them to CRLF, and the Prettier rule that
3
+ # `npm run lint` enforces wants LF — which produced thousands of "Delete `␍`" warnings that buried
4
+ # every real finding.
5
+ * text=auto eol=lf
6
+
7
+ # Binary assets must never be line-ending converted.
8
+ *.png binary
9
+ *.jpg binary
10
+ *.jpeg binary
11
+ *.gif binary
12
+ *.ttf binary
13
+ *.otf binary
14
+ *.aar binary
15
+ *.jar binary
16
+ *.keystore binary
17
+ *.tgz binary
package/CHANGELOG.md CHANGED
@@ -1,70 +1,120 @@
1
- # Changelog
2
-
3
- All notable changes to this package are documented here.
4
-
5
- This package is a fork of [`expo-video`](https://github.com/expo/expo/tree/main/packages/expo-video).
6
- Changes inherited from the upstream base version are not repeated here — see the
7
- [upstream changelog](https://github.com/expo/expo/blob/main/packages/expo-video/CHANGELOG.md)
8
- for the history of everything that isn't subtitle related.
9
-
10
- ## 0.1.2
11
-
12
- ### Fixed
13
-
14
- - **Android:** subtitles that are on screen at the same time are now stacked as one block instead of
15
- being drawn on top of each other. `.srt` files legitimately contain overlapping timestamps (two
16
- speakers, dialogue over on-screen text), but SubRip carries no positioning, so Media3 pinned every
17
- cue to the same spot and rendered them one over the other Media3 does the stacking in its WebVTT
18
- *parser* (`line = -1 - i`) and has no equivalent pass for SubRip. A `SubtitleParser` for SubRip now
19
- splits the cue timeline at every boundary and merges the cues active in each slice into a single
20
- multi-line cue, ordered like WebVTT: the cue that started earliest sits at the bottom. This also
21
- makes Android match the iOS output, which already stacked because sideloaded SRT is converted to
22
- WebVTT there. Cues with explicit `{\anN}` positioning are left untouched.
23
- - **Android:** selecting a `subtitleTrack` whose id is not among the available tracks no longer
24
- leaves the previously selected track on screen. The cleared track-selection parameters were only
25
- applied when a matching track was found, so an unmatched assignment silently did nothing; the
26
- selection is now cleared and a warning is logged.
27
- - **Android:** `subtitleStyle.bottomOffset` is reset to the default when it is omitted, instead of
28
- keeping the value from a previously applied style.
29
-
30
- ## 0.1.1
31
-
32
- ### Fixed
33
-
34
- - **Android:** `subtitleStyle.fontFamily` now resolves custom fonts bundled with the app, the same way
35
- React Native resolves a `<Text>` component's `fontFamily`. It is looked up as an Android font
36
- resource (`res/font`, where `expo-font`'s config plugin puts fonts) and then through React Native's
37
- font manager, which covers the `assets/fonts/<name>.ttf` convention and fonts registered at runtime.
38
- Previously only the built-in system families worked: `Typeface.create` silently fell back to the
39
- default typeface for any bundled font, so `fontFamily` appeared to do nothing on Android while
40
- working on iOS.
41
-
42
- ### Documentation
43
-
44
- - Document font support and the silent-fallback behaviour for unresolved family names.
45
-
46
- ## 0.1.0
47
-
48
- Initial release. Based on `expo-video` **56.1.4** (Expo SDK 56).
49
-
50
- ### Added
51
-
52
- - **External (sideloaded) subtitles** via `VideoSource.subtitleTracks`. Attach WebVTT or SubRip
53
- subtitle files (local or remote) to any source — most importantly progressive **MP4**, which,
54
- unlike HLS/DASH manifests, cannot advertise its own subtitle renditions. Sideloaded tracks are
55
- merged into the media and surface through `availableSubtitleTracks`, so they are selected with
56
- `subtitleTrack` exactly like embedded tracks.
57
- - Android: merged with `MediaItem.SubtitleConfiguration` (Media3 wraps them in a `MergingMediaSource`).
58
- - iOS: merged into an `AVMutableComposition` as a `.text` track. SRT is converted to WebVTT, remote
59
- files are cached locally first, and per-track time ranges are clamped to the video duration.
60
- Applies to progressive, non-DRM sources on the asynchronous loading path.
61
- - **Custom subtitle styling** via the `VideoView` `subtitleStyle` prop: `textColor`, `backgroundColor`,
62
- `windowColor`, `fontSize`, `fontFamily`, `bold`, `edgeType`, `edgeColor`, and `bottomOffset`.
63
- Applies to embedded and sideloaded tracks alike.
64
- - Android: `CaptionStyleCompat` on the `PlayerView` subtitle view.
65
- - iOS: `AVPlayerItem.textStyleRules`.
66
-
67
- ### Changed
68
-
69
- - The Android `publication` block and the iOS prebuild artifacts are removed, so the modified
70
- Kotlin and Swift are compiled from source instead of resolving a precompiled AAR/XCFramework.
1
+ # Changelog
2
+
3
+ All notable changes to this package are documented here.
4
+
5
+ This package is a fork of [`expo-video`](https://github.com/expo/expo/tree/main/packages/expo-video).
6
+ Changes inherited from the upstream base version are not repeated here — see the
7
+ [upstream changelog](https://github.com/expo/expo/blob/main/packages/expo-video/CHANGELOG.md)
8
+ for the history of everything that isn't subtitle related.
9
+
10
+ ## 0.2.0
11
+
12
+ ### Added
13
+
14
+ - **Android:** `subtitleStyle.applyEmbeddedStyles` keeps the inline formatting a subtitle track
15
+ carries instead of discarding it. Subtitle formats bring styling of their own — SubRip's `<b>`,
16
+ `<i>`, `<u>` and `<font color>` tags, and the richer per-cue styling of embedded WebVTT/TTML — and
17
+ until now every bit of it was thrown away so that `subtitleStyle` was the only thing deciding how
18
+ captions looked. `SubtitleView.setApplyEmbeddedStyles(false)` was hardcoded, and Media3 implements
19
+ that by removing every span from every cue, so an `.srt` written with `<i>` rendered upright.
20
+ Defaults to `false`, so nothing changes until you opt in. The rest of `subtitleStyle` still applies
21
+ underneath and `fontSize` still overrides any size the track asks for; the trade-off is that a cue
22
+ specifying its own colours overrides `textColor` and `windowColor` for that cue. There is no iOS
23
+ equivalent: `AVPlayerItem.textStyleRules` always composes with what the WebVTT cue declares.
24
+
25
+ ### Fixed
26
+
27
+ - **Android:** a subtitle no longer drops down a line when the subtitle *below* it ends. Cues that
28
+ are on screen together are drawn as one block anchored at its bottom, so the moment the lower cue
29
+ expired the upper one fell into its place — moving the text at exactly the spot the viewer is
30
+ reading. Every cue now keeps the same line for its whole time on screen: the line a finished cue
31
+ leaves behind is held empty, and the next cue to start reclaims the lowest empty line rather than
32
+ stacking on top, which is what keeps the block from creeping upwards. An empty line can therefore
33
+ sit below — or between — the cues still on screen, and the stack resets once nothing is left on
34
+ screen. Cues with explicit `{\anN}` positioning are still passed through untouched.
35
+ - **Android:** merging simultaneous SubRip cues no longer flattens their text. The cues were joined
36
+ through `toString()`, which erases the spans Media3's SubRip parser produces from inline tags. This
37
+ changed nothing on screen at the time, because the renderer stripped those spans anyway, but it
38
+ would have silently defeated the new `applyEmbeddedStyles` option for any overlapping cue.
39
+
40
+ ### Changed
41
+
42
+ - Repository tooling, with no effect on the published runtime: `npm run lint` runs again. The repo
43
+ had no `eslint.config.js` — `expo-module configure` treats it as an optional template and never
44
+ creates one while the `oxlint.config.mjs` it did have re-exported a module that
45
+ `expo-module-scripts@56` does not ship, so the lint step in `FORK.md` §5 had never actually
46
+ executed. A `.gitattributes` now pins the working copy to LF, which stops the Prettier rule from
47
+ reporting nearly every line of every file on a Windows checkout. The single lint error this
48
+ uncovered, a non-simple expression in a `useMemo` dependency list in `VideoPlayer.web.tsx`, is
49
+ fixed by hoisting it into a variable; the value and its identity semantics are unchanged.
50
+
51
+ ## 0.1.3
52
+
53
+ ### Documentation
54
+
55
+ - Document how subtitles that are on screen at the same time are stacked, which shipped in 0.1.2 but
56
+ was only described in the changelog.
57
+ - Add a release procedure to `FORK.md`, including the rule that every version bump carries a
58
+ changelog entry.
59
+
60
+ ## 0.1.2
61
+
62
+ ### Fixed
63
+
64
+ - **Android:** subtitles that are on screen at the same time are now stacked as one block instead of
65
+ being drawn on top of each other. `.srt` files legitimately contain overlapping timestamps (two
66
+ speakers, dialogue over on-screen text), but SubRip carries no positioning, so Media3 pinned every
67
+ cue to the same spot and rendered them one over the other — Media3 does the stacking in its WebVTT
68
+ *parser* (`line = -1 - i`) and has no equivalent pass for SubRip. A `SubtitleParser` for SubRip now
69
+ splits the cue timeline at every boundary and merges the cues active in each slice into a single
70
+ multi-line cue, ordered like WebVTT: the cue that started earliest sits at the bottom. This also
71
+ makes Android match the iOS output, which already stacked because sideloaded SRT is converted to
72
+ WebVTT there. Cues with explicit `{\anN}` positioning are left untouched.
73
+ - **Android:** selecting a `subtitleTrack` whose id is not among the available tracks no longer
74
+ leaves the previously selected track on screen. The cleared track-selection parameters were only
75
+ applied when a matching track was found, so an unmatched assignment silently did nothing; the
76
+ selection is now cleared and a warning is logged.
77
+ - **Android:** `subtitleStyle.bottomOffset` is reset to the default when it is omitted, instead of
78
+ keeping the value from a previously applied style.
79
+
80
+ ## 0.1.1
81
+
82
+ ### Fixed
83
+
84
+ - **Android:** `subtitleStyle.fontFamily` now resolves custom fonts bundled with the app, the same way
85
+ React Native resolves a `<Text>` component's `fontFamily`. It is looked up as an Android font
86
+ resource (`res/font`, where `expo-font`'s config plugin puts fonts) and then through React Native's
87
+ font manager, which covers the `assets/fonts/<name>.ttf` convention and fonts registered at runtime.
88
+ Previously only the built-in system families worked: `Typeface.create` silently fell back to the
89
+ default typeface for any bundled font, so `fontFamily` appeared to do nothing on Android while
90
+ working on iOS.
91
+
92
+ ### Documentation
93
+
94
+ - Document font support and the silent-fallback behaviour for unresolved family names.
95
+
96
+ ## 0.1.0
97
+
98
+ Initial release. Based on `expo-video` **56.1.4** (Expo SDK 56).
99
+
100
+ ### Added
101
+
102
+ - **External (sideloaded) subtitles** via `VideoSource.subtitleTracks`. Attach WebVTT or SubRip
103
+ subtitle files (local or remote) to any source — most importantly progressive **MP4**, which,
104
+ unlike HLS/DASH manifests, cannot advertise its own subtitle renditions. Sideloaded tracks are
105
+ merged into the media and surface through `availableSubtitleTracks`, so they are selected with
106
+ `subtitleTrack` exactly like embedded tracks.
107
+ - Android: merged with `MediaItem.SubtitleConfiguration` (Media3 wraps them in a `MergingMediaSource`).
108
+ - iOS: merged into an `AVMutableComposition` as a `.text` track. SRT is converted to WebVTT, remote
109
+ files are cached locally first, and per-track time ranges are clamped to the video duration.
110
+ Applies to progressive, non-DRM sources on the asynchronous loading path.
111
+ - **Custom subtitle styling** via the `VideoView` `subtitleStyle` prop: `textColor`, `backgroundColor`,
112
+ `windowColor`, `fontSize`, `fontFamily`, `bold`, `edgeType`, `edgeColor`, and `bottomOffset`.
113
+ Applies to embedded and sideloaded tracks alike.
114
+ - Android: `CaptionStyleCompat` on the `PlayerView` subtitle view.
115
+ - iOS: `AVPlayerItem.textStyleRules`.
116
+
117
+ ### Changed
118
+
119
+ - The Android `publication` block and the iOS prebuild artifacts are removed, so the modified
120
+ Kotlin and Swift are compiled from source instead of resolving a precompiled AAR/XCFramework.
package/FORK.md CHANGED
@@ -23,17 +23,20 @@ Measured against the published `expo-video@56.1.4` tarball. `+`/`-` are added/re
23
23
  | `ios/Records/SubtitleStyle.swift` | 118 | Record for caption styling; hex→ARGB parsing; builds `[AVTextStyleRule]`. |
24
24
  | `ios/VideoPlayerSubtitleSideload.swift` | 203 | Builds the `AVMutableComposition`, downloads remote subs, SRT→WebVTT converter. |
25
25
  | `android/…/records/SubtitleSource.kt` | 54 | Record → `MediaItem.SubtitleConfiguration` + MIME resolution. |
26
- | `android/…/records/SubtitleStyle.kt` | 129 | Record → `CaptionStyleCompat`; hex colour parsing; font-family resolution (`res/font` → `ReactFontManager`). |
27
- | `android/…/utils/StackingSubtitleParser.kt` | 214 | `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. |
26
+ | `android/…/records/SubtitleStyle.kt` | 137 | Record → `CaptionStyleCompat`; hex colour parsing; font-family resolution (`res/font` → `ReactFontManager`); the `applyEmbeddedStyles` flag, which `SubtitleUtils` reads directly rather than `toCaptionStyle`. |
27
+ | `android/…/utils/StackingSubtitleParser.kt` | 297 | `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. |
28
28
  | `FORK.md`, `LICENSE` | — | This guide; MIT licence (upstream copyright preserved). |
29
+ | `eslint.config.js` | 4 | Verbatim copy of `expo-module-scripts/templates/eslint.config.js`. **Recreate it if a sync drops it** — `expo-module configure` treats it as an *optional* template and only syncs it when it already exists, so without this file `npm run lint` dies with "ESLint couldn't find an eslint.config.js". |
30
+ | `.gitattributes` | 21 | Pins the working copy to LF. Git stores these files with LF, but a Windows checkout rewrites them to CRLF and the Prettier rule `npm run lint` enforces wants LF, which drowned the output in ~2800 "Delete `␍`" warnings. |
29
31
 
30
32
  ### Modified — upstream files carrying fork edits (re-apply these after a sync)
31
33
 
32
34
  | File | Δ | What to re-apply |
33
35
  | --- | --- | --- |
34
36
  | `src/VideoPlayer.types.ts` | +59 | `subtitleTracks?: SubtitleSource[]` on `VideoSourceObject`; the exported `SubtitleSource` type. |
35
- | `src/VideoView.types.ts` | +94 | `subtitleStyle?: SubtitleStyle` on `VideoViewProps`; `SubtitleStyle` + `SubtitleEdgeType` types. |
37
+ | `src/VideoView.types.ts` | +114 | `subtitleStyle?: SubtitleStyle` on `VideoViewProps`; `SubtitleStyle` + `SubtitleEdgeType` types. |
36
38
  | `src/index.ts` | +2 | Export `SubtitleStyle`, `SubtitleEdgeType` (`SubtitleSource` rides on `export type *`). |
39
+ | `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. |
37
40
  | `src/ts-declarations/react-native-assets.d.ts` | +22 / −1 | Replace upstream's `../../../expo-asset/…` reference (monorepo-only) with standalone `declare module` shims. **Always needed outside the Expo monorepo.** |
38
41
  | `ios/Records/VideoSource.swift` | +3 | `@Field var subtitleTracks: [SubtitleSource]?`. |
39
42
  | `ios/VideoPlayerItem.swift` | +17 / −2 | In the **async** init: hoist `isHls` to a local, and build the sideload composition for non-HLS/non-DRM sources before `super.init(asset:)`. |
@@ -41,7 +44,7 @@ Measured against the published `expo-video@56.1.4` tarball. `+`/`-` are added/re
41
44
  | `ios/VideoView.swift` | +8 | `subtitleStyle` property; forward it when `player` is (re)assigned. |
42
45
  | `ios/VideoModule.swift` | +4 | `Prop("subtitleStyle")`. |
43
46
  | `android/…/records/VideoSource.kt` | +9 / −2 | `subtitleTracks` field; `setSubtitleConfigurations(…)` in `toMediaItem()`; subtitle key in `toMediaId()`. |
44
- | `android/…/utils/SubtitleUtils.kt` | +27 / −16 | `configureSubtitleView(…, customStyle)` overload and a `styleProvider` on the captioning listener. Keeps the original behaviour when no custom style is set. Passes `context` to `toCaptionStyle` for font lookup. |
47
+ | `android/…/utils/SubtitleUtils.kt` | +33 / −17 | `configureSubtitleView(…, customStyle)` overload and a `styleProvider` on the captioning listener. Keeps the original behaviour when no custom style is set. Passes `context` to `toCaptionStyle` for font lookup. Drives `setApplyEmbeddedStyles` from `customStyle.applyEmbeddedStyles` (upstream hardcodes `false`). |
45
48
  | `android/…/VideoView.kt` | +12 / −5 | `subtitleStyle` property; pass it at all 4 `configureSubtitleView` call sites + the captioning listener. |
46
49
  | `android/…/VideoModule.kt` | +4 | `Prop("subtitleStyle")`. |
47
50
  | `android/…/utils/DataSourceUtils.kt` | +5 | `setSubtitleParserFactory(ExpoVideoSubtitleParserFactory())` on the `DefaultMediaSourceFactory`. Note this file declares `package expo.modules.video` despite living in `utils/`, so the factory needs an explicit import. |
@@ -58,10 +61,11 @@ Measured against the published `expo-video@56.1.4` tarball. `+`/`-` are added/re
58
61
  | `local-maven-repo/` (Android AAR) | Upstream ships a **precompiled** `expo.modules.video` AAR. If present, Gradle resolves it and **silently ignores the modified Kotlin**. |
59
62
  | `prebuilds/` (iOS XCFramework) | Same trap on iOS: CocoaPods links the prebuilt `ExpoVideo.xcframework` instead of compiling the modified Swift. |
60
63
  | `spm.config.json` | Swift Package Manager prebuild config; meaningless once we build from source. |
64
+ | `oxlint.config.mjs` | Re-exported `expo-module-scripts/oxlint.config.base`, which does not exist in `expo-module-scripts@56` — that package lints with ESLint and ships no oxlint base. A dead file that only misled readers into thinking oxlint was the configured linter. |
61
65
 
62
66
  > **This is the single most important thing to preserve.** If a sync re-introduces the `publication`
63
67
  > block, `local-maven-repo/` or `prebuilds/`, the app keeps building — it just runs **upstream's**
64
- > video module, and every subtitle feature silently disappears. Verify with §4.
68
+ > video module, and every subtitle feature silently disappears. Verify with §5.
65
69
 
66
70
  ### External API coupling to watch
67
71
 
@@ -105,9 +109,12 @@ Then re-apply the fork:
105
109
  with real deletions are `SubtitleUtils.kt` (function rewritten), `VideoView.kt`/`FullscreenPlayerActivity.kt`
106
110
  (call-site signatures), `VideoPlayerItem.swift` (`isHls` hoist) and the ts-declarations shim.
107
111
  7. **Remove the Android `publication` block** from `expo-module.config.json`.
108
- 8. **Restore the fork's `package.json`** (§3) and keep `README.md`, `CHANGELOG.md`, `LICENSE`, `FORK.md`.
112
+ 8. **Restore the fork's `package.json`** (§3) and keep `README.md`, `CHANGELOG.md`, `LICENSE`,
113
+ `FORK.md`, `eslint.config.js` and `.gitattributes`. Step 3 only replaces `src ios android plugin`,
114
+ so the root-level files survive on their own — but confirm it, because a missing `eslint.config.js`
115
+ breaks `npm run lint` in a way that reads like a tooling bug rather than a lost file.
109
116
  9. Update the baseline version in this file, the README, and add a `CHANGELOG.md` entry.
110
- 10. Verify with §4, then commit.
117
+ 10. Verify with §5, then commit.
111
118
 
112
119
  ---
113
120
 
@@ -127,31 +134,125 @@ Upstream's `package.json` will overwrite these if copied blindly. Keep:
127
134
 
128
135
  ---
129
136
 
130
- ## 4. Verification
137
+ ## 4. Releasing
131
138
 
132
- Run all four. The native ones are what actually prove the fork survived the sync.
139
+ **Every version bump gets a `CHANGELOG.md` entry no exceptions, including one-line fixes.**
140
+ The changelog is the only place a consumer can see what changed between two installs, and this
141
+ package ships native code, so a bump can silently change runtime behaviour on a device.
142
+
143
+ For each release:
144
+
145
+ 1. Decide the version (semver against the *fork's* own API, not the upstream `expo-video` version):
146
+ - **patch** — bug fix, no API change.
147
+ - **minor** — new option/prop/field, or a behaviour change consumers may notice.
148
+ - **major** — anything that breaks an existing call site.
149
+ 2. Add a `## <version>` section at the top of `CHANGELOG.md`, above the previous release, grouped
150
+ under `### Added` / `### Fixed` / `### Changed` / `### Documentation`. Write it for someone who
151
+ does not know the codebase: what they will observe, and on which platform. Say *why* when the
152
+ cause is non-obvious — the entries for 0.1.1 and 0.1.2 are the reference for the level of detail.
153
+ 3. `npm version <version> --no-git-tag-version`.
154
+ 4. Update the docs the change touches — `README.md` for anything user-facing (a new option, a
155
+ changed rendering behaviour), and §1 of this file if files were added, removed or newly modified.
156
+ 5. Verify with §5, then commit and push.
157
+ 6. `npm publish` (its `prepublishOnly` cleans and rebuilds; never publish from a dirty tree).
158
+
159
+ When a release syncs a new upstream `expo-video`, note the new baseline version in the changelog
160
+ entry, in §1 of this file and in the README's intro line.
161
+
162
+ ## 5. Verification
163
+
164
+ > **This package is standalone.** Nothing in this section may reach into an unrelated project's
165
+ > checkout. Verifying by overlaying the package onto some other app's `node_modules` silently mutates
166
+ > a repository that has nothing to do with this one, and couples a public npm package to a private
167
+ > codebase. If a step needs a host app, create a throwaway one for that purpose.
168
+
169
+ ### Self-contained — run these on every change
133
170
 
134
171
  ```bash
135
172
  npm run typecheck # must be clean
136
- npm run lint
173
+ npm run lint # must exit 0; warnings are tolerated, errors are not
137
174
  npm pack --dry-run # expect build/, plugin/build/, LICENSE, native sources;
138
175
  # NO node_modules, prebuilds, local-maven-repo
139
176
  ```
140
177
 
141
- **Native compile** because both platforms default to precompiled artifacts, you must build from
142
- source inside a host app. Overlay this package into the app's `node_modules/expo-video`, then:
178
+ `npm run lint` needs `eslint.config.js` at the repo root `expo-module configure` will **not**
179
+ create it (it is an optional template that only gets synced when it already exists), so a sync that
180
+ loses the file silently breaks this step. See §1.
181
+
182
+ ### Android — compiling the parser without a host app
183
+
184
+ `utils/StackingSubtitleParser.kt` imports only `android.text.*` and `androidx.media3.*`, so it
185
+ compiles on its own. This is the file that carries almost all of the Android subtitle logic, and it
186
+ is the one most likely to break when Media3 changes `SubtitleParser`, `CuesWithTiming` or `Cue`.
187
+
188
+ In a scratch directory, with `<media3>` set to `androidxMedia3Version` from `android/build.gradle`
189
+ and `<sdk>` to an installed Android platform:
190
+
191
+ ```kotlin
192
+ // settings.gradle.kts -> rootProject.name = "kotlincheck"
193
+ // build.gradle.kts:
194
+ import java.util.zip.ZipFile
195
+
196
+ plugins { kotlin("jvm") version "2.1.21" }
197
+ repositories { google(); mavenCentral() }
198
+
199
+ val media3: Configuration by configurations.creating
200
+ dependencies {
201
+ media3("androidx.media3:media3-common:<media3>")
202
+ media3("androidx.media3:media3-extractor:<media3>")
203
+ media3("androidx.media3:media3-ui:<media3>")
204
+ }
205
+
206
+ // A plain JVM compile cannot read .aar, so pull classes.jar out of each one.
207
+ val unpacked = layout.buildDirectory.dir("unpacked")
208
+ val unpackAars by tasks.registering {
209
+ outputs.dir(unpacked)
210
+ doLast {
211
+ val out = unpacked.get().asFile.also { it.deleteRecursively(); it.mkdirs() }
212
+ media3.forEach { f ->
213
+ if (f.name.endsWith(".aar")) {
214
+ ZipFile(f).use { zip ->
215
+ zip.getEntry("classes.jar")?.let { e ->
216
+ zip.getInputStream(e).use { it.copyTo(File(out, f.name.removeSuffix(".aar") + ".jar").outputStream()) }
217
+ }
218
+ }
219
+ } else if (f.name.endsWith(".jar")) f.copyTo(File(out, f.name), overwrite = true)
220
+ }
221
+ }
222
+ }
223
+
224
+ tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
225
+ dependsOn(unpackAars)
226
+ libraries.from(fileTree(unpacked) { include("*.jar") }, files("<sdk>/platforms/android-36/android.jar"))
227
+ }
228
+ ```
229
+
230
+ Copy the file to `src/main/kotlin/expo/modules/video/utils/` and run `gradle compileKotlin` —
231
+ expect `BUILD SUCCESSFUL`. Because the class implements Media3's `SubtitleParser`, a successful
232
+ build also proves the three overrides still match that Media3 version's interface.
233
+
234
+ The rest of the Android sources import `expo.modules.kotlin` and `com.facebook.react`, neither of
235
+ which is on a public Maven repository, so they cannot be checked this way — a full build is the only
236
+ option for them.
237
+
238
+ ### Full native build — needs a throwaway host app
239
+
240
+ Both platforms default to precompiled artifacts, so a complete check means building from source in a
241
+ host app. Create one for this purpose (`npx create-expo-app`), install this package into it, and
242
+ confirm §1's "Removed" artifacts are absent from the installed copy:
143
243
 
144
244
  ```bash
145
245
  # Android — expect BUILD SUCCESSFUL
146
- cd <app>/android && ./gradlew :expo-video:compileDebugKotlin
246
+ cd <scratch-app>/android && ./gradlew :expo-video-subtitle:compileDebugKotlin
147
247
 
148
248
  # iOS — expect BUILD SUCCEEDED
149
- cd <app>/ios && pod install
249
+ cd <scratch-app>/ios && pod install
150
250
  xcodebuild -project Pods/Pods.xcodeproj -target ExpoVideo -sdk iphoneos \
151
251
  -configuration Debug build CODE_SIGNING_ALLOWED=NO
152
252
  ```
153
253
 
154
- Back up `node_modules/expo-video` first and restore it (plus `pod install`) afterwards.
254
+ `npm pack` the fork and install the tarball rather than editing the app's `node_modules` in place,
255
+ so the thing you compile is exactly what a consumer would receive.
155
256
 
156
257
  **Smoke test in the app:** an MP4 source with `subtitleTracks` must list the tracks in
157
258
  `player.availableSubtitleTracks`, render when selected via `player.subtitleTrack`, and visibly change
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- The MIT License (MIT)
2
-
3
- Copyright (c) 2015-present 650 Industries, Inc. (aka Expo)
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015-present 650 Industries, Inc. (aka Expo)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.