From 09e9462ac04e4adfcaa96e0bd2cae5394c7a8a28 Mon Sep 17 00:00:00 2001
From: herrrta <73949927+herrrta@users.noreply.github.com>
Date: Tue, 25 Feb 2025 23:10:15 -0500
Subject: [PATCH] feat: (iOS) Switch Video Players
---
.gitattributes | 1 +
.gitignore | 1 +
app/(auth)/player/direct-player.tsx | 4 +-
components/settings/OtherSettings.tsx | 33 +-
components/video-player/controls/Controls.tsx | 89 ++--
.../controls/contexts/ControlContext.tsx | 1 -
.../controls/contexts/VideoContext.tsx | 2 +-
components/vlc/VideoDebugInfo.tsx | 2 +-
.../{vlc-player/src => }/VlcPlayer.types.ts | 0
.../{vlc-player/src => }/VlcPlayerView.tsx | 18 +-
modules/index.ts | 27 ++
modules/vlc-player-3/expo-module.config.json | 6 +
modules/vlc-player-3/ios/VlcPlayer3.podspec | 23 ++
.../vlc-player-3/ios/VlcPlayer3Module.swift | 71 ++++
modules/vlc-player-3/ios/VlcPlayer3View.swift | 388 ++++++++++++++++++
modules/vlc-player-3/src/VlcPlayer3Module.ts | 5 +
.../.gradle/8.9/checksums/checksums.lock | Bin 17 -> 0 bytes
.../8.9/dependencies-accessors/gc.properties | 0
.../.gradle/8.9/fileChanges/last-build.bin | Bin 1 -> 0 bytes
.../.gradle/8.9/fileHashes/fileHashes.lock | Bin 17 -> 0 bytes
.../android/.gradle/8.9/gc.properties | 0
.../buildOutputCleanup.lock | Bin 17 -> 0 bytes
.../buildOutputCleanup/cache.properties | 2 -
.../android/.gradle/vcs-1/gc.properties | 0
modules/vlc-player/index.ts | 68 ---
modules/vlc-player/ios/VlcPlayer.podspec | 3 +-
modules/vlc-player/ios/VlcPlayerView.swift | 1 -
translations/de.json | 7 +-
translations/en.json | 7 +-
translations/es.json | 7 +-
translations/fr.json | 7 +-
translations/it.json | 7 +-
translations/ja.json | 7 +-
translations/nl.json | 7 +-
translations/tr.json | 7 +-
translations/zh-CN.json | 7 +-
translations/zh-TW.json | 7 +-
utils/atoms/settings.ts | 9 +
38 files changed, 676 insertions(+), 148 deletions(-)
create mode 100644 .gitattributes
rename modules/{vlc-player/src => }/VlcPlayer.types.ts (100%)
rename modules/{vlc-player/src => }/VlcPlayerView.tsx (86%)
create mode 100644 modules/index.ts
create mode 100644 modules/vlc-player-3/expo-module.config.json
create mode 100644 modules/vlc-player-3/ios/VlcPlayer3.podspec
create mode 100644 modules/vlc-player-3/ios/VlcPlayer3Module.swift
create mode 100644 modules/vlc-player-3/ios/VlcPlayer3View.swift
create mode 100644 modules/vlc-player-3/src/VlcPlayer3Module.ts
delete mode 100644 modules/vlc-player/android/.gradle/8.9/checksums/checksums.lock
delete mode 100644 modules/vlc-player/android/.gradle/8.9/dependencies-accessors/gc.properties
delete mode 100644 modules/vlc-player/android/.gradle/8.9/fileChanges/last-build.bin
delete mode 100644 modules/vlc-player/android/.gradle/8.9/fileHashes/fileHashes.lock
delete mode 100644 modules/vlc-player/android/.gradle/8.9/gc.properties
delete mode 100644 modules/vlc-player/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock
delete mode 100644 modules/vlc-player/android/.gradle/buildOutputCleanup/cache.properties
delete mode 100644 modules/vlc-player/android/.gradle/vcs-1/gc.properties
delete mode 100644 modules/vlc-player/index.ts
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 00000000..56dea966
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+.modules/vlc-player/Frameworks/*.xcframework filter=lfs diff=lfs merge=lfs -text
diff --git a/.gitignore b/.gitignore
index 2a0ce8db..00eb8098 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,6 +10,7 @@ npm-debug.*
*.orig.*
web-build/
modules/vlc-player/android/build
+modules/vlc-player/android/.gradle
bun.lockb
# macOS
diff --git a/app/(auth)/player/direct-player.tsx b/app/(auth)/player/direct-player.tsx
index 9a407ade..83bda8ce 100644
--- a/app/(auth)/player/direct-player.tsx
+++ b/app/(auth)/player/direct-player.tsx
@@ -5,13 +5,13 @@ import { Controls } from "@/components/video-player/controls/Controls";
import { getDownloadedFileUrl } from "@/hooks/useDownloadedFileOpener";
import { useInvalidatePlaybackProgressCache } from "@/hooks/useRevalidatePlaybackProgressCache";
import { useWebSocket } from "@/hooks/useWebsockets";
-import { VlcPlayerView } from "@/modules/vlc-player";
+import { VlcPlayerView } from "@/modules";
import {
PipStartedPayload,
PlaybackStatePayload,
ProgressUpdatePayload,
VlcPlayerViewRef,
-} from "@/modules/vlc-player/src/VlcPlayer.types";
+} from "@/modules/VlcPlayer.types";
const downloadProvider = !Platform.isTV ? require("@/providers/DownloadProvider") : null;
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
import { getStreamUrl } from "@/utils/jellyfin/media/getStreamUrl";
diff --git a/components/settings/OtherSettings.tsx b/components/settings/OtherSettings.tsx
index e14c00cd..b947b9a6 100644
--- a/components/settings/OtherSettings.tsx
+++ b/components/settings/OtherSettings.tsx
@@ -1,5 +1,5 @@
import { Platform } from "react-native";
-import { ScreenOrientationEnum, useSettings } from "@/utils/atoms/settings";
+import {ScreenOrientationEnum, useSettings, VideoPlayer} from "@/utils/atoms/settings";
import { BitrateSelector, BITRATES } from "@/components/BitrateSelector";
import {
BACKGROUND_FETCH_TASK,
@@ -22,6 +22,7 @@ import { ListItem } from "../list/ListItem";
import { useTranslation } from "react-i18next";
import DisabledSetting from "@/components/settings/DisabledSetting";
import Dropdown from "@/components/common/Dropdown";
+import {isNumber} from "lodash";
export const OtherSettings: React.FC = () => {
const router = useRouter();
@@ -142,6 +143,36 @@ export const OtherSettings: React.FC = () => {
/>
+ {(Platform.OS === "ios" || Platform.isTVOS)&& (
+
+ t(`home.settings.other.video_players.${VideoPlayer[item]}`)}
+ title={
+
+
+ {t(`home.settings.other.video_players.${VideoPlayer[settings.defaultPlayer]}`)}
+
+
+
+ }
+ label={t("home.settings.other.orientation")}
+ onSelected={(defaultPlayer) =>
+ updateSettings({ defaultPlayer })
+ }
+ />
+
+ )}
+
= ({
)}
- {!Platform.isTV && (
+ {!Platform.isTV && settings.defaultPlayer == VideoPlayer.VLC_4 && (
) => void;
}
-const NativeViewManager = requireNativeViewManager("VlcPlayer");
+const VLCViewManager = requireNativeViewManager("VlcPlayer");
+const VLC3ViewManager = requireNativeViewManager("VlcPlayer3");
// Create a forwarded ref version of the native view
const NativeView = React.forwardRef(
- (props, ref) =>
+ (props, ref) => {
+ const [settings] = useSettings();
+
+ if (Platform.OS === "ios" || Platform.isTVOS) {
+ if (settings.defaultPlayer == VideoPlayer.VLC_3) {
+ console.log("[Apple] Using Vlc Player 3")
+ return
+ }
+ }
+ console.log("Using default Vlc Player")
+ return
+ }
);
const VlcPlayerView = React.forwardRef(
diff --git a/modules/index.ts b/modules/index.ts
new file mode 100644
index 00000000..397fc0eb
--- /dev/null
+++ b/modules/index.ts
@@ -0,0 +1,27 @@
+import VlcPlayerView from "./VlcPlayerView";
+import {
+ PlaybackStatePayload,
+ ProgressUpdatePayload,
+ VideoLoadStartPayload,
+ VideoStateChangePayload,
+ VideoProgressPayload,
+ VlcPlayerSource,
+ TrackInfo,
+ ChapterInfo,
+ VlcPlayerViewProps,
+ VlcPlayerViewRef,
+} from "./VlcPlayer.types";
+
+export {
+ VlcPlayerView,
+ VlcPlayerViewProps,
+ VlcPlayerViewRef,
+ PlaybackStatePayload,
+ ProgressUpdatePayload,
+ VideoLoadStartPayload,
+ VideoStateChangePayload,
+ VideoProgressPayload,
+ VlcPlayerSource,
+ TrackInfo,
+ ChapterInfo,
+};
diff --git a/modules/vlc-player-3/expo-module.config.json b/modules/vlc-player-3/expo-module.config.json
new file mode 100644
index 00000000..1e6766d7
--- /dev/null
+++ b/modules/vlc-player-3/expo-module.config.json
@@ -0,0 +1,6 @@
+{
+ "platforms": ["ios", "tvos"],
+ "ios": {
+ "modules": ["VlcPlayer3Module"]
+ }
+}
diff --git a/modules/vlc-player-3/ios/VlcPlayer3.podspec b/modules/vlc-player-3/ios/VlcPlayer3.podspec
new file mode 100644
index 00000000..15274a12
--- /dev/null
+++ b/modules/vlc-player-3/ios/VlcPlayer3.podspec
@@ -0,0 +1,23 @@
+Pod::Spec.new do |s|
+ s.name = 'VlcPlayer3'
+ s.version = '3.6.1b1'
+ s.summary = 'A sample project summary'
+ s.description = 'A sample project description'
+ s.author = ''
+ s.homepage = 'https://docs.expo.dev/modules/'
+ s.platforms = { :ios => '13.4', :tvos => '13.4' }
+ s.source = { git: '' }
+ s.static_framework = true
+
+ s.dependency 'ExpoModulesCore'
+ s.ios.dependency 'MobileVLCKit', s.version
+ s.tvos.dependency 'TVVLCKit', s.version
+
+ # Swift/Objective-C compatibility
+ s.pod_target_xcconfig = {
+ 'DEFINES_MODULE' => 'YES',
+ 'SWIFT_COMPILATION_MODE' => 'wholemodule'
+ }
+
+ s.source_files = "*.{h,m,mm,swift,hpp,cpp}"
+end
diff --git a/modules/vlc-player-3/ios/VlcPlayer3Module.swift b/modules/vlc-player-3/ios/VlcPlayer3Module.swift
new file mode 100644
index 00000000..c0e32606
--- /dev/null
+++ b/modules/vlc-player-3/ios/VlcPlayer3Module.swift
@@ -0,0 +1,71 @@
+import ExpoModulesCore
+
+public class VlcPlayer3Module: Module {
+ public func definition() -> ModuleDefinition {
+ Name("VlcPlayer3")
+ View(VlcPlayer3View.self) {
+ Prop("source") { (view: VlcPlayer3View, source: [String: Any]) in
+ view.setSource(source)
+ }
+
+ Prop("paused") { (view: VlcPlayer3View, paused: Bool) in
+ if paused {
+ view.pause()
+ } else {
+ view.play()
+ }
+ }
+
+ Events(
+ "onPlaybackStateChanged",
+ "onVideoStateChange",
+ "onVideoLoadStart",
+ "onVideoLoadEnd",
+ "onVideoProgress",
+ "onVideoError",
+ "onPipStarted"
+ )
+
+ AsyncFunction("startPictureInPicture") { (view: VlcPlayer3View) in
+ view.startPictureInPicture()
+ }
+
+ AsyncFunction("play") { (view: VlcPlayer3View) in
+ view.play()
+ }
+
+ AsyncFunction("pause") { (view: VlcPlayer3View) in
+ view.pause()
+ }
+
+ AsyncFunction("stop") { (view: VlcPlayer3View) in
+ view.stop()
+ }
+
+ AsyncFunction("seekTo") { (view: VlcPlayer3View, time: Int32) in
+ view.seekTo(time)
+ }
+
+ AsyncFunction("setAudioTrack") { (view: VlcPlayer3View, trackIndex: Int) in
+ view.setAudioTrack(trackIndex)
+ }
+
+ AsyncFunction("getAudioTracks") { (view: VlcPlayer3View) -> [[String: Any]]? in
+ return view.getAudioTracks()
+ }
+
+ AsyncFunction("setSubtitleTrack") { (view: VlcPlayer3View, trackIndex: Int) in
+ view.setSubtitleTrack(trackIndex)
+ }
+
+ AsyncFunction("getSubtitleTracks") { (view: VlcPlayer3View) -> [[String: Any]]? in
+ return view.getSubtitleTracks()
+ }
+
+ AsyncFunction("setSubtitleURL") {
+ (view: VlcPlayer3View, url: String, name: String) in
+ view.setSubtitleURL(url, name: name)
+ }
+ }
+ }
+}
diff --git a/modules/vlc-player-3/ios/VlcPlayer3View.swift b/modules/vlc-player-3/ios/VlcPlayer3View.swift
new file mode 100644
index 00000000..b9189f9f
--- /dev/null
+++ b/modules/vlc-player-3/ios/VlcPlayer3View.swift
@@ -0,0 +1,388 @@
+import ExpoModulesCore
+#if os(tvOS)
+import TVVLCKit
+#else
+import MobileVLCKit
+#endif
+import UIKit
+
+class VlcPlayer3View: ExpoView {
+ private var mediaPlayer: VLCMediaPlayer?
+ private var videoView: UIView?
+ private var progressUpdateInterval: TimeInterval = 1.0 // Update interval set to 1 second
+ private var isPaused: Bool = false
+ private var currentGeometryCString: [CChar]?
+ private var lastReportedState: VLCMediaPlayerState?
+ private var lastReportedIsPlaying: Bool?
+ private var customSubtitles: [(internalName: String, originalName: String)] = []
+ private var startPosition: Int32 = 0
+ private var isMediaReady: Bool = false
+ private var externalTrack: [String: String]?
+ private var progressTimer: DispatchSourceTimer?
+ private var isStopping: Bool = false // Define isStopping here
+ private var lastProgressCall = Date().timeIntervalSince1970
+ var hasSource = false
+
+ // MARK: - Initialization
+
+ required init(appContext: AppContext? = nil) {
+ super.init(appContext: appContext)
+ setupView()
+ setupNotifications()
+ }
+
+ // MARK: - Setup
+
+ private func setupView() {
+ DispatchQueue.main.async {
+ self.backgroundColor = .black
+ self.videoView = UIView()
+ self.videoView?.translatesAutoresizingMaskIntoConstraints = false
+
+ if let videoView = self.videoView {
+ self.addSubview(videoView)
+ NSLayoutConstraint.activate([
+ videoView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
+ videoView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
+ videoView.topAnchor.constraint(equalTo: self.topAnchor),
+ videoView.bottomAnchor.constraint(equalTo: self.bottomAnchor),
+ ])
+ }
+ }
+ }
+
+ private func setupNotifications() {
+ NotificationCenter.default.addObserver(
+ self, selector: #selector(applicationWillResignActive),
+ name: UIApplication.willResignActiveNotification, object: nil)
+ NotificationCenter.default.addObserver(
+ self, selector: #selector(applicationDidBecomeActive),
+ name: UIApplication.didBecomeActiveNotification, object: nil)
+ }
+
+ // MARK: - Public Methods
+ func startPictureInPicture() { }
+
+ @objc func play() {
+ self.mediaPlayer?.play()
+ self.isPaused = false
+ print("Play")
+ }
+
+ @objc func pause() {
+ self.mediaPlayer?.pause()
+ self.isPaused = true
+ }
+
+ @objc func seekTo(_ time: Int32) {
+ guard let player = self.mediaPlayer else { return }
+
+ let wasPlaying = player.isPlaying
+ if wasPlaying {
+ self.pause()
+ }
+
+ if let duration = player.media?.length.intValue {
+ print("Seeking to time: \(time) Video Duration \(duration)")
+
+ // If the specified time is greater than the duration, seek to the end
+ let seekTime = time > duration ? duration - 1000 : time
+ player.time = VLCTime(int: seekTime)
+
+ if wasPlaying {
+ self.play()
+ }
+ self.updatePlayerState()
+ } else {
+ print("Error: Unable to retrieve video duration")
+ }
+ }
+
+ @objc func setSource(_ source: [String: Any]) {
+ DispatchQueue.main.async { [weak self] in
+ guard let self = self else { return }
+ if self.hasSource {
+ return
+ }
+
+ let mediaOptions = source["mediaOptions"] as? [String: Any] ?? [:]
+ self.externalTrack = source["externalTrack"] as? [String: String]
+ var initOptions = source["initOptions"] as? [Any] ?? []
+ self.startPosition = source["startPosition"] as? Int32 ?? 0
+ initOptions.append("--start-time=\(self.startPosition)")
+
+ guard let uri = source["uri"] as? String, !uri.isEmpty else {
+ print("Error: Invalid or empty URI")
+ self.onVideoError?(["error": "Invalid or empty URI"])
+ return
+ }
+
+ let autoplay = source["autoplay"] as? Bool ?? false
+ let isNetwork = source["isNetwork"] as? Bool ?? false
+
+ self.onVideoLoadStart?(["target": self.reactTag ?? NSNull()])
+ self.mediaPlayer = VLCMediaPlayer(options: initOptions)
+ self.mediaPlayer?.delegate = self
+ self.mediaPlayer?.drawable = self.videoView
+ self.mediaPlayer?.scaleFactor = 0
+
+ let media: VLCMedia
+ if isNetwork {
+ print("Loading network file: \(uri)")
+ media = VLCMedia(url: URL(string: uri)!)
+ } else {
+ print("Loading local file: \(uri)")
+ if uri.starts(with: "file://"), let url = URL(string: uri) {
+ media = VLCMedia(url: url)
+ } else {
+ media = VLCMedia(path: uri)
+ }
+ }
+
+ print("Debug: Media options: \(mediaOptions)")
+ media.addOptions(mediaOptions)
+
+ self.mediaPlayer?.media = media
+ self.hasSource = true
+
+ if autoplay {
+ print("Playing...")
+ self.play()
+ }
+ }
+ }
+
+ @objc func setAudioTrack(_ trackIndex: Int) {
+ self.mediaPlayer?.currentAudioTrackIndex = Int32(trackIndex)
+ }
+
+ @objc func getAudioTracks() -> [[String: Any]]? {
+ guard let trackNames = mediaPlayer?.audioTrackNames,
+ let trackIndexes = mediaPlayer?.audioTrackIndexes
+ else {
+ return nil
+ }
+
+ return zip(trackNames, trackIndexes).map { name, index in
+ return ["name": name, "index": index]
+ }
+ }
+
+ @objc func setSubtitleTrack(_ trackIndex: Int) {
+ print("Debug: Attempting to set subtitle track to index: \(trackIndex)")
+ self.mediaPlayer?.currentVideoSubTitleIndex = Int32(trackIndex)
+ print(
+ "Debug: Current subtitle track index after setting: \(self.mediaPlayer?.currentVideoSubTitleIndex ?? -1)"
+ )
+ }
+
+ @objc func setSubtitleURL(_ subtitleURL: String, name: String) {
+ guard let url = URL(string: subtitleURL) else {
+ print("Error: Invalid subtitle URL")
+ return
+ }
+
+ let result = self.mediaPlayer?.addPlaybackSlave(url, type: .subtitle, enforce: true)
+ if let result = result {
+ let internalName = "Track \(self.customSubtitles.count + 1)"
+ print("Subtitle added with result: \(result) \(internalName)")
+ self.customSubtitles.append((internalName: internalName, originalName: name))
+ } else {
+ print("Failed to add subtitle")
+ }
+ }
+
+ @objc func getSubtitleTracks() -> [[String: Any]]? {
+ guard let mediaPlayer = self.mediaPlayer else {
+ return nil
+ }
+
+ let count = mediaPlayer.numberOfSubtitlesTracks
+ print("Debug: Number of subtitle tracks: \(count)")
+
+ guard count > 0 else {
+ return nil
+ }
+
+ var tracks: [[String: Any]] = []
+
+ if let names = mediaPlayer.videoSubTitlesNames as? [String],
+ let indexes = mediaPlayer.videoSubTitlesIndexes as? [NSNumber]
+ {
+ for (index, name) in zip(indexes, names) {
+ if let customSubtitle = customSubtitles.first(where: { $0.internalName == name }) {
+ tracks.append(["name": customSubtitle.originalName, "index": index.intValue])
+ } else {
+ tracks.append(["name": name, "index": index.intValue])
+ }
+ }
+ }
+
+ print("Debug: Subtitle tracks: \(tracks)")
+ return tracks
+ }
+
+ @objc func stop(completion: (() -> Void)? = nil) {
+ guard !isStopping else {
+ completion?()
+ return
+ }
+ isStopping = true
+
+ // If we're not on the main thread, dispatch to main thread
+ if !Thread.isMainThread {
+ DispatchQueue.main.async { [weak self] in
+ self?.performStop(completion: completion)
+ }
+ } else {
+ performStop(completion: completion)
+ }
+ }
+
+ // MARK: - Private Methods
+
+ @objc private func applicationWillResignActive() {
+
+ }
+
+ @objc private func applicationDidBecomeActive() {
+
+ }
+
+ private func performStop(completion: (() -> Void)? = nil) {
+ // Stop the media player
+ mediaPlayer?.stop()
+
+ // Remove observer
+ NotificationCenter.default.removeObserver(self)
+
+ // Clear the video view
+ videoView?.removeFromSuperview()
+ videoView = nil
+
+ // Release the media player
+ mediaPlayer?.delegate = nil
+ mediaPlayer = nil
+
+ isStopping = false
+ completion?()
+ }
+
+ private func updateVideoProgress() {
+ guard let player = self.mediaPlayer else { return }
+
+ let currentTimeMs = player.time.intValue
+ let durationMs = player.media?.length.intValue ?? 0
+
+ print("Debug: Current time: \(currentTimeMs)")
+ if currentTimeMs >= 0 && currentTimeMs < durationMs {
+ if player.isPlaying && !self.isMediaReady {
+ self.isMediaReady = true
+ // Set external track subtitle when starting.
+ if let externalTrack = self.externalTrack {
+ if let name = externalTrack["name"], !name.isEmpty {
+ let deliveryUrl = externalTrack["DeliveryUrl"] ?? ""
+ self.setSubtitleURL(deliveryUrl, name: name)
+ }
+ }
+ }
+ self.onVideoProgress?([
+ "currentTime": currentTimeMs,
+ "duration": durationMs,
+ ])
+ }
+ }
+
+ // MARK: - Expo Events
+
+ @objc var onPlaybackStateChanged: RCTDirectEventBlock?
+ @objc var onVideoLoadStart: RCTDirectEventBlock?
+ @objc var onVideoStateChange: RCTDirectEventBlock?
+ @objc var onVideoProgress: RCTDirectEventBlock?
+ @objc var onVideoLoadEnd: RCTDirectEventBlock?
+ @objc var onVideoError: RCTDirectEventBlock?
+ @objc var onPipStarted: RCTDirectEventBlock?
+
+ // MARK: - Deinitialization
+
+ deinit {
+ performStop()
+ }
+}
+
+extension VlcPlayer3View: VLCMediaPlayerDelegate {
+ func mediaPlayerTimeChanged(_ aNotification: Notification) {
+ // self?.updateVideoProgress()
+ let timeNow = Date().timeIntervalSince1970
+ if timeNow - lastProgressCall >= 1 {
+ lastProgressCall = timeNow
+ updateVideoProgress()
+ }
+ }
+
+ func mediaPlayerStateChanged(_ aNotification: Notification) {
+ self.updatePlayerState()
+ }
+
+ private func updatePlayerState() {
+ guard let player = self.mediaPlayer else { return }
+ let currentState = player.state
+
+ var stateInfo: [String: Any] = [
+ "target": self.reactTag ?? NSNull(),
+ "currentTime": player.time.intValue,
+ "duration": player.media?.length.intValue ?? 0,
+ "error": false,
+ ]
+
+ if player.isPlaying {
+ stateInfo["isPlaying"] = true
+ stateInfo["isBuffering"] = false
+ stateInfo["state"] = "Playing"
+ } else {
+ stateInfo["isPlaying"] = false
+ stateInfo["state"] = "Paused"
+ }
+
+ if player.state == VLCMediaPlayerState.buffering {
+ stateInfo["isBuffering"] = true
+ stateInfo["state"] = "Buffering"
+ } else if player.state == VLCMediaPlayerState.error {
+ print("player.state ~ error")
+ stateInfo["state"] = "Error"
+ self.onVideoLoadEnd?(stateInfo)
+ } else if player.state == VLCMediaPlayerState.opening {
+ print("player.state ~ opening")
+ stateInfo["state"] = "Opening"
+ }
+
+ if self.lastReportedState != currentState
+ || self.lastReportedIsPlaying != player.isPlaying
+ {
+ self.lastReportedState = currentState
+ self.lastReportedIsPlaying = player.isPlaying
+ self.onVideoStateChange?(stateInfo)
+ }
+
+ }
+}
+
+extension VlcPlayer3View: VLCMediaDelegate {
+ // Implement VLCMediaDelegate methods if needed
+}
+
+extension VLCMediaPlayerState {
+ var description: String {
+ switch self {
+ case .opening: return "Opening"
+ case .buffering: return "Buffering"
+ case .playing: return "Playing"
+ case .paused: return "Paused"
+ case .stopped: return "Stopped"
+ case .ended: return "Ended"
+ case .error: return "Error"
+ case .esAdded: return "ESAdded"
+ @unknown default: return "Unknown"
+ }
+ }
+}
diff --git a/modules/vlc-player-3/src/VlcPlayer3Module.ts b/modules/vlc-player-3/src/VlcPlayer3Module.ts
new file mode 100644
index 00000000..b292aaff
--- /dev/null
+++ b/modules/vlc-player-3/src/VlcPlayer3Module.ts
@@ -0,0 +1,5 @@
+import { requireNativeModule } from 'expo-modules-core';
+
+// It loads the native module object from the JSI or falls back to
+// the bridge module (from NativeModulesProxy) if the remote debugger is on.
+export default requireNativeModule('VlcPlayer3');
diff --git a/modules/vlc-player/android/.gradle/8.9/checksums/checksums.lock b/modules/vlc-player/android/.gradle/8.9/checksums/checksums.lock
deleted file mode 100644
index 52a7f0f4db1362d9d70601745a03ec2299581cfd..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 17
TcmZQxc(L*Ap=GabGC% void
-): EventSubscription {
- return emitter.addListener(
- "onPlaybackStateChanged",
- listener
- );
-}
-
-export function addVideoLoadStartListener(
- listener: (event: VideoLoadStartPayload) => void
-): EventSubscription {
- return emitter.addListener(
- "onVideoLoadStart",
- listener
- );
-}
-
-export function addVideoStateChangeListener(
- listener: (event: VideoStateChangePayload) => void
-): EventSubscription {
- return emitter.addListener(
- "onVideoStateChange",
- listener
- );
-}
-
-export function addVideoProgressListener(
- listener: (event: VideoProgressPayload) => void
-): EventSubscription {
- return emitter.addListener("onVideoProgress", listener);
-}
-
-export {
- VlcPlayerView,
- VlcPlayerViewProps,
- VlcPlayerViewRef,
- PlaybackStatePayload,
- ProgressUpdatePayload,
- VideoLoadStartPayload,
- VideoStateChangePayload,
- VideoProgressPayload,
- VlcPlayerSource,
- TrackInfo,
- ChapterInfo,
-};
diff --git a/modules/vlc-player/ios/VlcPlayer.podspec b/modules/vlc-player/ios/VlcPlayer.podspec
index 89e84814..46dbafd1 100644
--- a/modules/vlc-player/ios/VlcPlayer.podspec
+++ b/modules/vlc-player/ios/VlcPlayer.podspec
@@ -19,6 +19,5 @@ Pod::Spec.new do |s|
'DEFINES_MODULE' => 'YES',
'SWIFT_COMPILATION_MODE' => 'wholemodule'
}
-
- s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}"
+ s.source_files = "*.{h,m,mm,swift,hpp,cpp}"
end
diff --git a/modules/vlc-player/ios/VlcPlayerView.swift b/modules/vlc-player/ios/VlcPlayerView.swift
index c40b5108..f02478d2 100644
--- a/modules/vlc-player/ios/VlcPlayerView.swift
+++ b/modules/vlc-player/ios/VlcPlayerView.swift
@@ -3,7 +3,6 @@ import UIKit
import VLCKit
import os
-
public class VLCPlayerView: UIView {
func setupView(parent: UIView) {
self.backgroundColor = .black
diff --git a/translations/de.json b/translations/de.json
index 962e10d7..cca7b183 100644
--- a/translations/de.json
+++ b/translations/de.json
@@ -128,7 +128,12 @@
"OTHER": "Andere",
"UNKNOWN": "Unbekannt"
},
- "safe_area_in_controls": "Sicherer Bereich in den Steuerungen",
+ "safe_area_in_controls": "Sicherer Bereich in den Steuerungen",
+ "video_player": "Video player",
+ "video_players": {
+ "VLC_3": "VLC 3",
+ "VLC_4": "VLC 4 (Experimental + PiP)"
+ },
"show_custom_menu_links": "Benutzerdefinierte Menülinks anzeigen",
"hide_libraries": "Bibliotheken ausblenden",
"select_liraries_you_want_to_hide": "Wähl die Bibliotheken aus, die du im Bibliothekstab und auf der Startseite ausblenden möchtest.",
diff --git a/translations/en.json b/translations/en.json
index 5ba25ec9..40594300 100644
--- a/translations/en.json
+++ b/translations/en.json
@@ -129,11 +129,16 @@
"UNKNOWN": "Unknown"
},
"safe_area_in_controls": "Safe area in controls",
+ "video_player": "Video player",
+ "video_players": {
+ "VLC_3": "VLC 3",
+ "VLC_4": "VLC 4 (Experimental + PiP)"
+ },
"show_custom_menu_links": "Show Custom Menu Links",
"hide_libraries": "Hide Libraries",
"select_liraries_you_want_to_hide": "Select the libraries you want to hide from the Library tab and home page sections.",
"disable_haptic_feedback": "Disable Haptic Feedback",
- "default_quality": "Default quality"
+ "default_quality": "Default quality",
},
"downloads": {
"downloads_title": "Downloads",
diff --git a/translations/es.json b/translations/es.json
index 9004c2f8..9a2962a3 100644
--- a/translations/es.json
+++ b/translations/es.json
@@ -128,7 +128,12 @@
"OTHER": "Otra",
"UNKNOWN": "Desconocida"
},
- "safe_area_in_controls": "Área segura en controles",
+ "safe_area_in_controls": "Área segura en controles",
+ "video_player": "Video player",
+ "video_players": {
+ "VLC_3": "VLC 3",
+ "VLC_4": "VLC 4 (Experimental + PiP)"
+ },
"show_custom_menu_links": "Mostrar enlaces de menú personalizados",
"hide_libraries": "Ocultar bibliotecas",
"select_liraries_you_want_to_hide": "Selecciona las bibliotecas que quieres ocultar de la pestaña Bibliotecas y de Inicio.",
diff --git a/translations/fr.json b/translations/fr.json
index 7347d274..719b5fae 100644
--- a/translations/fr.json
+++ b/translations/fr.json
@@ -128,7 +128,12 @@
"OTHER": "Autre",
"UNKNOWN": "Inconnu"
},
- "safe_area_in_controls": "Zone de sécurité dans les contrôles",
+ "safe_area_in_controls": "Zone de sécurité dans les contrôles",
+ "video_player": "Video player",
+ "video_players": {
+ "VLC_3": "VLC 3",
+ "VLC_4": "VLC 4 (Experimental + PiP)"
+ },
"show_custom_menu_links": "Afficher les liens personnalisés",
"hide_libraries": "Cacher des bibliothèques",
"select_liraries_you_want_to_hide": "Sélectionnez les bibliothèques que vous souhaitez masquer dans l’onglet Bibliothèque et les sections de la page d’accueil.",
diff --git a/translations/it.json b/translations/it.json
index 60a95bb1..fc713f8e 100644
--- a/translations/it.json
+++ b/translations/it.json
@@ -128,7 +128,12 @@
"OTHER": "Altro",
"UNKNOWN": "Sconosciuto"
},
- "safe_area_in_controls": "Area sicura per i controlli",
+ "safe_area_in_controls": "Area sicura per i controlli",
+ "video_player": "Video player",
+ "video_players": {
+ "VLC_3": "VLC 3",
+ "VLC_4": "VLC 4 (Experimental + PiP)"
+ },
"show_custom_menu_links": "Mostra i link del menu personalizzato",
"hide_libraries": "Nascondi Librerie",
"select_liraries_you_want_to_hide": "Selezionate le librerie che volete nascondere dalla scheda Libreria e dalle sezioni della pagina iniziale.",
diff --git a/translations/ja.json b/translations/ja.json
index acd99ff2..085b6c3d 100644
--- a/translations/ja.json
+++ b/translations/ja.json
@@ -128,7 +128,12 @@
"OTHER": "その他",
"UNKNOWN": "不明"
},
- "safe_area_in_controls": "コントロールの安全エリア",
+ "safe_area_in_controls": "コントロールの安全エリア",
+ "video_player": "Video player",
+ "video_players": {
+ "VLC_3": "VLC 3",
+ "VLC_4": "VLC 4 (Experimental + PiP)"
+ },
"show_custom_menu_links": "カスタムメニューのリンクを表示",
"hide_libraries": "ライブラリを非表示",
"select_liraries_you_want_to_hide": "ライブラリタブとホームページセクションから非表示にするライブラリを選択します。",
diff --git a/translations/nl.json b/translations/nl.json
index d2129263..0e44a305 100644
--- a/translations/nl.json
+++ b/translations/nl.json
@@ -128,7 +128,12 @@
"OTHER": "Andere",
"UNKNOWN": "Onbekend"
},
- "safe_area_in_controls": "Veilig gebied in bedieningen",
+ "safe_area_in_controls": "Veilig gebied in bedieningen",
+ "video_player": "Video player",
+ "video_players": {
+ "VLC_3": "VLC 3",
+ "VLC_4": "VLC 4 (Experimental + PiP)"
+ },
"show_custom_menu_links": "Aangepaste menulinks tonen",
"hide_libraries": "Verberg Bibliotheken",
"select_liraries_you_want_to_hide": "Selecteer de bibliotheken die je wil verbergen van de Bibliotheektab en hoofdpagina onderdelen.",
diff --git a/translations/tr.json b/translations/tr.json
index bdc140c8..a9c65b02 100644
--- a/translations/tr.json
+++ b/translations/tr.json
@@ -128,7 +128,12 @@
"OTHER": "Diğer",
"UNKNOWN": "Bilinmeyen"
},
- "safe_area_in_controls": "Kontrollerde Güvenli Alan",
+ "safe_area_in_controls": "Kontrollerde Güvenli Alan",
+ "video_player": "Video player",
+ "video_players": {
+ "VLC_3": "VLC 3",
+ "VLC_4": "VLC 4 (Experimental + PiP)"
+ },
"show_custom_menu_links": "Özel Menü Bağlantılarını Göster",
"hide_libraries": "Kütüphaneleri Gizle",
"select_liraries_you_want_to_hide": "Kütüphane sekmesinden ve ana sayfa bölümlerinden gizlemek istediğiniz kütüphaneleri seçin.",
diff --git a/translations/zh-CN.json b/translations/zh-CN.json
index 94c1dad5..2fb3abf9 100644
--- a/translations/zh-CN.json
+++ b/translations/zh-CN.json
@@ -128,7 +128,12 @@
"OTHER": "其他",
"UNKNOWN": "未知"
},
- "safe_area_in_controls": "控制中的安全区域",
+ "safe_area_in_controls": "控制中的安全区域",
+ "video_player": "Video player",
+ "video_players": {
+ "VLC_3": "VLC 3",
+ "VLC_4": "VLC 4 (Experimental + PiP)"
+ },
"show_custom_menu_links": "显示自定义菜单链接",
"hide_libraries": "隐藏媒体库",
"select_liraries_you_want_to_hide": "选择您想从媒体库页面和主页隐藏的媒体库。",
diff --git a/translations/zh-TW.json b/translations/zh-TW.json
index 2dbd287e..3f127a6b 100644
--- a/translations/zh-TW.json
+++ b/translations/zh-TW.json
@@ -128,7 +128,12 @@
"OTHER": "其他",
"UNKNOWN": "未知"
},
- "safe_area_in_controls": "控制中的安全區域",
+ "safe_area_in_controls": "控制中的安全區域",
+ "video_player": "Video player",
+ "video_players": {
+ "VLC_3": "VLC 3",
+ "VLC_4": "VLC 4 (Experimental + PiP)"
+ },
"show_custom_menu_links": "顯示自定義菜單鏈接",
"hide_libraries": "隱藏媒體庫",
"select_liraries_you_want_to_hide": "選擇您想從媒體庫頁面和主頁隱藏的媒體庫。",
diff --git a/utils/atoms/settings.ts b/utils/atoms/settings.ts
index 2426be4f..a1ba7d0b 100644
--- a/utils/atoms/settings.ts
+++ b/utils/atoms/settings.ts
@@ -14,6 +14,7 @@ import {
import { Bitrate, BITRATES } from "@/components/BitrateSelector";
import { apiAtom } from "@/providers/JellyfinProvider";
import { writeInfoLog } from "@/utils/log";
+import {Video} from "@/utils/jellyseerr/server/models/Movie";
const STREAMYFIN_PLUGIN_ID = "1e9e5d386e6746158719e98a5c34f004";
const STREAMYFIN_PLUGIN_SETTINGS = "STREAMYFIN_PLUGIN_SETTINGS";
@@ -112,6 +113,12 @@ export type HomeSectionNextUpResolver = {
enableRewatching?: boolean;
};
+export enum VideoPlayer {
+ // NATIVE, //todo: changes will make this a lot more easier to implement if we want. delete if not wanted
+ VLC_3,
+ VLC_4
+}
+
export type Settings = {
home?: Home | null;
autoRotate?: boolean;
@@ -146,6 +153,7 @@ export type Settings = {
jellyseerrServerUrl?: string;
hiddenLibraries?: string[];
enableH265ForChromecast: boolean;
+ defaultPlayer: VideoPlayer;
};
export interface Lockable {
@@ -200,6 +208,7 @@ const defaultValues: Settings = {
jellyseerrServerUrl: undefined,
hiddenLibraries: [],
enableH265ForChromecast: false,
+ defaultPlayer: VideoPlayer.VLC_3, // ios only setting. does not matter what this is for android
};
const loadSettings = (): Partial => {