diff --git a/app/(auth)/player/direct-player.tsx b/app/(auth)/player/direct-player.tsx index b418a139..5b1e18ad 100644 --- a/app/(auth)/player/direct-player.tsx +++ b/app/(auth)/player/direct-player.tsx @@ -516,7 +516,7 @@ export default function page() { return () => setIsMounted(false); }, []); - if (itemStatus.isLoading || streamStatus.isLoading) { + if (itemStatus.isLoading || streamStatus.isLoading || !item || !stream) { return ( diff --git a/components/video-player/controls/contexts/VideoContext.tsx b/components/video-player/controls/contexts/VideoContext.tsx index ca1344d6..6b643b29 100644 --- a/components/video-player/controls/contexts/VideoContext.tsx +++ b/components/video-player/controls/contexts/VideoContext.tsx @@ -9,7 +9,7 @@ import { useState, } from "react"; import type { TrackInfo } from "@/modules/VlcPlayer.types"; -import { useSettings, VideoPlayer } from "@/utils/atoms/settings"; +import { useSettings } from "@/utils/atoms/settings"; import type { Track } from "../types"; import { useControlContext } from "./ControlContext"; @@ -48,7 +48,7 @@ export const VideoProvider: React.FC = ({ }) => { const [audioTracks, setAudioTracks] = useState(null); const [subtitleTracks, setSubtitleTracks] = useState(null); - const [settings] = useSettings(); + const [_settings] = useSettings(); const ControlContext = useControlContext(); const isVideoLoaded = ControlContext?.isVideoLoaded; @@ -136,7 +136,7 @@ export const VideoProvider: React.FC = ({ ); // Step 2: Apply VLC indexing logic - let textSubIndex = settings.defaultPlayer === VideoPlayer.VLC_4 ? 0 : 1; + let textSubIndex = 0; const processedSubs: Track[] = sortedSubs?.map((sub) => { // Always increment for non-transcoding subtitles // Only increment for text-based subtitles when transcoding diff --git a/modules/VlcPlayerView.tsx b/modules/VlcPlayerView.tsx index 70775876..750b6ba2 100644 --- a/modules/VlcPlayerView.tsx +++ b/modules/VlcPlayerView.tsx @@ -1,8 +1,6 @@ import { requireNativeViewManager } from "expo-modules-core"; import * as React from "react"; - -import { VideoPlayer, useSettings } from "@/utils/atoms/settings"; -import { Platform, ViewStyle } from "react-native"; +import { ViewStyle } from "react-native"; import type { VlcPlayerSource, VlcPlayerViewProps, @@ -14,22 +12,10 @@ interface NativeViewRef extends VlcPlayerViewRef { } const VLCViewManager = requireNativeViewManager("VlcPlayer"); -const VLC3ViewManager = requireNativeViewManager("VlcPlayer3"); // Create a forwarded ref version of the native view const NativeView = React.forwardRef( - (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 ; - }, + (props, ref) => , ); const VlcPlayerView = React.forwardRef( diff --git a/modules/vlc-player-3/expo-module.config.json b/modules/vlc-player-3/expo-module.config.json deleted file mode 100644 index 1e6766d7..00000000 --- a/modules/vlc-player-3/expo-module.config.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "platforms": ["ios", "tvos"], - "ios": { - "modules": ["VlcPlayer3Module"] - } -} diff --git a/modules/vlc-player-3/ios/VlcPlayer3.podspec b/modules/vlc-player-3/ios/VlcPlayer3.podspec deleted file mode 100644 index 15274a12..00000000 --- a/modules/vlc-player-3/ios/VlcPlayer3.podspec +++ /dev/null @@ -1,23 +0,0 @@ -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 deleted file mode 100644 index c0e32606..00000000 --- a/modules/vlc-player-3/ios/VlcPlayer3Module.swift +++ /dev/null @@ -1,71 +0,0 @@ -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 deleted file mode 100644 index 50882734..00000000 --- a/modules/vlc-player-3/ios/VlcPlayer3View.swift +++ /dev/null @@ -1,392 +0,0 @@ -import ExpoModulesCore - -#if os(tvOS) - import TVVLCKit -#else - import MobileVLCKit -#endif - -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 externalSubtitles: [[String: String]]? - 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 - self.externalSubtitles = source["externalSubtitles"] as? [[String: String]] - 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.setInitialExternalSubtitles() - 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: false) - if let result = result { - let internalName = "Track \(self.customSubtitles.count)" - print("Subtitle added with result: \(result) \(internalName)") - self.customSubtitles.append((internalName: internalName, originalName: name)) - } else { - print("Failed to add subtitle") - } - } - - private func setInitialExternalSubtitles() { - if let externalSubtitles = self.externalSubtitles { - for subtitle in externalSubtitles { - if let subtitleName = subtitle["name"], - let subtitleURL = subtitle["DeliveryUrl"] - { - print("Setting external subtitle: \(subtitleName) \(subtitleURL)") - self.setSubtitleURL(subtitleURL, name: subtitleName) - } - } - } - } - - @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 { - 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 deleted file mode 100644 index c0501304..00000000 --- a/modules/vlc-player-3/src/VlcPlayer3Module.ts +++ /dev/null @@ -1,5 +0,0 @@ -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/ios/VlcPlayer.podspec b/modules/vlc-player/ios/VlcPlayer.podspec index 5c88f736..b233a89b 100644 --- a/modules/vlc-player/ios/VlcPlayer.podspec +++ b/modules/vlc-player/ios/VlcPlayer.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'VlcPlayer' - s.version = '4.0.0a10' + s.version = '4.0.0a13' s.summary = 'A sample project summary' s.description = 'A sample project description' s.author = '' diff --git a/modules/vlc-player/ios/VlcPlayerModule.swift b/modules/vlc-player/ios/VlcPlayerModule.swift index 38299392..2bf44589 100644 --- a/modules/vlc-player/ios/VlcPlayerModule.swift +++ b/modules/vlc-player/ios/VlcPlayerModule.swift @@ -68,4 +68,4 @@ public class VlcPlayerModule: Module { } } } -} +} \ No newline at end of file diff --git a/modules/vlc-player/ios/VlcPlayerView.swift b/modules/vlc-player/ios/VlcPlayerView.swift index f02478d2..55e22d5c 100644 --- a/modules/vlc-player/ios/VlcPlayerView.swift +++ b/modules/vlc-player/ios/VlcPlayerView.swift @@ -94,7 +94,7 @@ extension VLCPlayerWrapper: VLCPictureInPictureMediaControlling { } func isMediaSeekable() -> Bool { - return player.isSeekable + return false } func isMediaPlaying() -> Bool { @@ -118,6 +118,7 @@ extension VLCPlayerWrapper: VLCMediaPlayerDelegate { func mediaPlayerTimeChanged(_ aNotification: Notification) { DispatchQueue.main.async { [weak self] in guard let self = self else { return } + self.performInitialSeek() let timeNow = Date().timeIntervalSince1970 if timeNow - self.lastProgressCall >= 1 { self.lastProgressCall = timeNow @@ -135,11 +136,22 @@ extension VLCPlayerWrapper: VLCMediaPlayerDelegate { pipController.invalidatePlaybackState() } } -} -// MARK: - VLCMediaDelegate -extension VLCPlayerWrapper: VLCMediaDelegate { - // Implement VLCMediaDelegate methods if needed + // Workaround: When playing an HLS video for the first time, seeking to a specific time immediately can cause a crash. + // To avoid this, we wait until the video has started playing before performing the initial seek. + func performInitialSeek() { + guard let vlcPlayerView = self.playerView.superview as? VlcPlayerView, + !vlcPlayerView.initialSeekPerformed, + vlcPlayerView.startPosition > 0, + vlcPlayerView.isTranscoding, + player.isSeekable else { return } + vlcPlayerView.initialSeekPerformed = true + // Use a logger from the VlcPlayerView if available, or create a new one + let logger = (vlcPlayerView).logger + logger.debug("First time update, performing initial seek to \(vlcPlayerView.startPosition) seconds") + player.time = VLCTime(int: vlcPlayerView.startPosition * 1000) + self.updateVideoProgress?() + } } class VlcPlayerView: ExpoView { @@ -149,11 +161,13 @@ class VlcPlayerView: ExpoView { private var progressUpdateInterval: TimeInterval = 1.0 // Update interval set to 1 second private var isPaused: Bool = false private var customSubtitles: [(internalName: String, originalName: String)] = [] - private var startPosition: Int32 = 0 + var startPosition: Int32 = 0 private var externalTrack: [String: String]? private var isStopping: Bool = false // Define isStopping here private var externalSubtitles: [[String: String]]? var hasSource = false + var initialSeekPerformed = false + var isTranscoding: Bool = false // MARK: - Initialization required init(appContext: AppContext? = nil) { @@ -251,6 +265,9 @@ class VlcPlayerView: ExpoView { return } + if uri.contains("m3u8") { + self.isTranscoding = true + } let autoplay = source["autoplay"] as? Bool ?? false let isNetwork = source["isNetwork"] as? Bool ?? false @@ -277,8 +294,11 @@ class VlcPlayerView: ExpoView { self.hasSource = true if autoplay { logger.info("Playing...") + // The Video is not transcoding so it its safe to seek to the start position. + if !self.isTranscoding { + self.vlc.player.time = VLCTime(number: NSNumber(value: self.startPosition * 1000)) + } self.play() - self.vlc.player.time = VLCTime(number: NSNumber(value: self.startPosition * 1000)) } } } diff --git a/utils/atoms/settings.ts b/utils/atoms/settings.ts index d21d3839..8f8bbf5e 100644 --- a/utils/atoms/settings.ts +++ b/utils/atoms/settings.ts @@ -1,8 +1,3 @@ -import { BITRATES, type Bitrate } from "@/components/BitrateSelector"; -import * as ScreenOrientation from "@/packages/expo-screen-orientation"; -import { apiAtom } from "@/providers/JellyfinProvider"; -import { Video } from "@/utils/jellyseerr/server/models/Movie"; -import { writeInfoLog } from "@/utils/log"; import { type BaseItemKind, type CultureDto, @@ -14,9 +9,13 @@ import { import { atom, useAtom, useAtomValue } from "jotai"; import { useCallback, useEffect, useMemo } from "react"; import { Platform } from "react-native"; +import { BITRATES, type Bitrate } from "@/components/BitrateSelector"; +import * as ScreenOrientation from "@/packages/expo-screen-orientation"; +import { apiAtom } from "@/providers/JellyfinProvider"; +import { writeInfoLog } from "@/utils/log"; import { storage } from "../mmkv"; -const STREAMYFIN_PLUGIN_ID = "1e9e5d386e6746158719e98a5c34f004"; +const _STREAMYFIN_PLUGIN_ID = "1e9e5d386e6746158719e98a5c34f004"; const STREAMYFIN_PLUGIN_SETTINGS = "STREAMYFIN_PLUGIN_SETTINGS"; export type DownloadQuality = "original" | "high" | "low"; @@ -129,7 +128,6 @@ export type HomeSectionLatestResolver = { export enum VideoPlayer { // NATIVE, //todo: changes will make this a lot more easier to implement if we want. delete if not wanted - VLC_3 = 0, VLC_4 = 1, } @@ -288,7 +286,7 @@ export const useSettings = () => { writeInfoLog("Got plugin settings", data?.settings); return data?.settings; }, - (err) => undefined, + (_err) => undefined, ); setPluginSettings(settings); return settings;