mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-04-22 08:44:41 +01:00
refactor: Feature/offline mode rework (#859)
Co-authored-by: lostb1t <coding-mosses0z@icloud.com> Co-authored-by: Fredrik Burmester <fredrik.burmester@gmail.com> Co-authored-by: Gauvain <68083474+Gauvino@users.noreply.github.com> Co-authored-by: Gauvino <uruknarb20@gmail.com> Co-authored-by: storm1er <le.storm1er@gmail.com> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Chris <182387676+whoopsi-daisy@users.noreply.github.com> Co-authored-by: arch-fan <55891793+arch-fan@users.noreply.github.com> Co-authored-by: Alex Kim <alexkim@Alexs-MacBook-Pro.local>
This commit is contained in:
7
modules/vlc-player-4/expo-module.config.json
Normal file
7
modules/vlc-player-4/expo-module.config.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"platforms": ["ios", "tvos"],
|
||||
"ios": {
|
||||
"modules": ["VlcPlayer4Module"],
|
||||
"appDelegateSubscribers": ["AppLifecycleDelegate"]
|
||||
}
|
||||
}
|
||||
32
modules/vlc-player-4/ios/AppLifecycleDelegate.swift
Normal file
32
modules/vlc-player-4/ios/AppLifecycleDelegate.swift
Normal file
@@ -0,0 +1,32 @@
|
||||
import ExpoModulesCore
|
||||
|
||||
protocol SimpleAppLifecycleListener {
|
||||
func applicationDidEnterBackground() -> Void
|
||||
func applicationDidEnterForeground() -> Void
|
||||
}
|
||||
|
||||
public class AppLifecycleDelegate: ExpoAppDelegateSubscriber {
|
||||
public func applicationDidBecomeActive(_ application: UIApplication) {
|
||||
// The app has become active.
|
||||
}
|
||||
|
||||
public func applicationWillResignActive(_ application: UIApplication) {
|
||||
// The app is about to become inactive.
|
||||
}
|
||||
|
||||
public func applicationDidEnterBackground(_ application: UIApplication) {
|
||||
VLCManager.shared.listeners.forEach { listener in
|
||||
listener.applicationDidEnterBackground()
|
||||
}
|
||||
}
|
||||
|
||||
public func applicationWillEnterForeground(_ application: UIApplication) {
|
||||
VLCManager.shared.listeners.forEach { listener in
|
||||
listener.applicationDidEnterForeground()
|
||||
}
|
||||
}
|
||||
|
||||
public func applicationWillTerminate(_ application: UIApplication) {
|
||||
// The app is about to terminate.
|
||||
}
|
||||
}
|
||||
4
modules/vlc-player-4/ios/VLCManager.swift
Normal file
4
modules/vlc-player-4/ios/VLCManager.swift
Normal file
@@ -0,0 +1,4 @@
|
||||
class VLCManager {
|
||||
static let shared = VLCManager()
|
||||
var listeners: [SimpleAppLifecycleListener] = []
|
||||
}
|
||||
22
modules/vlc-player-4/ios/VlcPlayer4.podspec
Normal file
22
modules/vlc-player-4/ios/VlcPlayer4.podspec
Normal file
@@ -0,0 +1,22 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'VlcPlayer4'
|
||||
s.version = '4.0.0a10'
|
||||
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 => '16' }
|
||||
s.source = { git: '' }
|
||||
s.static_framework = true
|
||||
|
||||
s.dependency 'ExpoModulesCore'
|
||||
s.ios.dependency 'VLCKit', s.version
|
||||
s.tvos.dependency 'VLCKit', 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
|
||||
71
modules/vlc-player-4/ios/VlcPlayer4Module.swift
Normal file
71
modules/vlc-player-4/ios/VlcPlayer4Module.swift
Normal file
@@ -0,0 +1,71 @@
|
||||
import ExpoModulesCore
|
||||
|
||||
public class VlcPlayer4Module: Module {
|
||||
public func definition() -> ModuleDefinition {
|
||||
Name("VlcPlayer4")
|
||||
View(VlcPlayer4View.self) {
|
||||
Prop("source") { (view: VlcPlayer4View, source: [String: Any]) in
|
||||
view.setSource(source)
|
||||
}
|
||||
|
||||
Prop("paused") { (view: VlcPlayer4View, paused: Bool) in
|
||||
if paused {
|
||||
view.pause()
|
||||
} else {
|
||||
view.play()
|
||||
}
|
||||
}
|
||||
|
||||
Events(
|
||||
"onPlaybackStateChanged",
|
||||
"onVideoStateChange",
|
||||
"onVideoLoadStart",
|
||||
"onVideoLoadEnd",
|
||||
"onVideoProgress",
|
||||
"onVideoError",
|
||||
"onPipStarted"
|
||||
)
|
||||
|
||||
AsyncFunction("startPictureInPicture") { (view: VlcPlayer4View) in
|
||||
view.startPictureInPicture()
|
||||
}
|
||||
|
||||
AsyncFunction("play") { (view: VlcPlayer4View) in
|
||||
view.play()
|
||||
}
|
||||
|
||||
AsyncFunction("pause") { (view: VlcPlayer4View) in
|
||||
view.pause()
|
||||
}
|
||||
|
||||
AsyncFunction("stop") { (view: VlcPlayer4View) in
|
||||
view.stop()
|
||||
}
|
||||
|
||||
AsyncFunction("seekTo") { (view: VlcPlayer4View, time: Int32) in
|
||||
view.seekTo(time)
|
||||
}
|
||||
|
||||
AsyncFunction("setAudioTrack") { (view: VlcPlayer4View, trackIndex: Int) in
|
||||
view.setAudioTrack(trackIndex)
|
||||
}
|
||||
|
||||
AsyncFunction("getAudioTracks") { (view: VlcPlayer4View) -> [[String: Any]]? in
|
||||
return view.getAudioTracks()
|
||||
}
|
||||
|
||||
AsyncFunction("setSubtitleTrack") { (view: VlcPlayer4View, trackIndex: Int) in
|
||||
view.setSubtitleTrack(trackIndex)
|
||||
}
|
||||
|
||||
AsyncFunction("getSubtitleTracks") { (view: VlcPlayer4View) -> [[String: Any]]? in
|
||||
return view.getSubtitleTracks()
|
||||
}
|
||||
|
||||
AsyncFunction("setSubtitleURL") {
|
||||
(view: VlcPlayer4View, url: String, name: String) in
|
||||
view.setSubtitleURL(url, name: name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
507
modules/vlc-player-4/ios/VlcPlayer4View.swift
Normal file
507
modules/vlc-player-4/ios/VlcPlayer4View.swift
Normal file
@@ -0,0 +1,507 @@
|
||||
import ExpoModulesCore
|
||||
import UIKit
|
||||
import VLCKit
|
||||
import os
|
||||
|
||||
public class VLCPlayerView: UIView {
|
||||
func setupView(parent: UIView) {
|
||||
self.backgroundColor = .black
|
||||
self.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
self.leadingAnchor.constraint(equalTo: parent.leadingAnchor),
|
||||
self.trailingAnchor.constraint(equalTo: parent.trailingAnchor),
|
||||
self.topAnchor.constraint(equalTo: parent.topAnchor),
|
||||
self.bottomAnchor.constraint(equalTo: parent.bottomAnchor),
|
||||
])
|
||||
}
|
||||
|
||||
public override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
for subview in subviews {
|
||||
subview.frame = bounds
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class VLCPlayerWrapper: NSObject {
|
||||
private var lastProgressCall = Date().timeIntervalSince1970
|
||||
public var player: VLCMediaPlayer = VLCMediaPlayer()
|
||||
private var updatePlayerState: (() -> Void)?
|
||||
private var updateVideoProgress: (() -> Void)?
|
||||
private var playerView: VLCPlayerView = VLCPlayerView()
|
||||
public weak var pipController: VLCPictureInPictureWindowControlling?
|
||||
|
||||
override public init() {
|
||||
super.init()
|
||||
player.delegate = self
|
||||
player.drawable = self
|
||||
player.scaleFactor = 0
|
||||
}
|
||||
|
||||
public func setup(
|
||||
parent: UIView,
|
||||
updatePlayerState: (() -> Void)?,
|
||||
updateVideoProgress: (() -> Void)?
|
||||
) {
|
||||
self.updatePlayerState = updatePlayerState
|
||||
self.updateVideoProgress = updateVideoProgress
|
||||
|
||||
player.delegate = self
|
||||
parent.addSubview(playerView)
|
||||
playerView.setupView(parent: parent)
|
||||
}
|
||||
|
||||
public func getPlayerView() -> UIView {
|
||||
return playerView
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - VLCPictureInPictureDrawable
|
||||
extension VLCPlayerWrapper: VLCPictureInPictureDrawable {
|
||||
public func mediaController() -> (any VLCPictureInPictureMediaControlling)! {
|
||||
return self
|
||||
}
|
||||
|
||||
public func pictureInPictureReady() -> (((any VLCPictureInPictureWindowControlling)?) -> Void)!
|
||||
{
|
||||
return { [weak self] controller in
|
||||
self?.pipController = controller
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - VLCPictureInPictureMediaControlling
|
||||
extension VLCPlayerWrapper: VLCPictureInPictureMediaControlling {
|
||||
func mediaTime() -> Int64 {
|
||||
return player.time.value?.int64Value ?? 0
|
||||
}
|
||||
|
||||
func mediaLength() -> Int64 {
|
||||
return player.media?.length.value?.int64Value ?? 0
|
||||
}
|
||||
|
||||
func play() {
|
||||
player.play()
|
||||
}
|
||||
|
||||
func pause() {
|
||||
player.pause()
|
||||
}
|
||||
|
||||
func seek(by offset: Int64, completion: @escaping () -> Void) {
|
||||
player.jump(withOffset: Int32(offset), completion: completion)
|
||||
}
|
||||
|
||||
func isMediaSeekable() -> Bool {
|
||||
return player.isSeekable
|
||||
}
|
||||
|
||||
func isMediaPlaying() -> Bool {
|
||||
return player.isPlaying
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - VLCDrawable
|
||||
extension VLCPlayerWrapper: VLCDrawable {
|
||||
public func addSubview(_ view: UIView) {
|
||||
playerView.addSubview(view)
|
||||
}
|
||||
|
||||
public func bounds() -> CGRect {
|
||||
return playerView.bounds
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - VLCMediaPlayerDelegate
|
||||
extension VLCPlayerWrapper: VLCMediaPlayerDelegate {
|
||||
func mediaPlayerTimeChanged(_ aNotification: Notification) {
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
let timeNow = Date().timeIntervalSince1970
|
||||
if timeNow - self.lastProgressCall >= 1 {
|
||||
self.lastProgressCall = timeNow
|
||||
self.updateVideoProgress?()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func mediaPlayerStateChanged(_ state: VLCMediaPlayerState) {
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.updatePlayerState?()
|
||||
|
||||
guard let pipController = self.pipController else { return }
|
||||
pipController.invalidatePlaybackState()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
class VlcPlayer4View: ExpoView {
|
||||
let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "VlcPlayer4View")
|
||||
|
||||
private var vlc: VLCPlayerWrapper = VLCPlayerWrapper()
|
||||
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
|
||||
private var externalTrack: [String: String]?
|
||||
private var isStopping: Bool = false // Define isStopping here
|
||||
private var externalSubtitles: [[String: String]]?
|
||||
var hasSource = false
|
||||
var initialSeekPerformed = false
|
||||
// A flag variable determinging if we should perform the initial seek. Its either transcoding or offline playback. that makes
|
||||
var shouldPerformInitialSeek: Bool = false
|
||||
|
||||
|
||||
// MARK: - Initialization
|
||||
required init(appContext: AppContext? = nil) {
|
||||
super.init(appContext: appContext)
|
||||
setupVLC()
|
||||
setupNotifications()
|
||||
VLCManager.shared.listeners.append(self)
|
||||
}
|
||||
|
||||
// MARK: - Setup
|
||||
private func setupVLC() {
|
||||
vlc.setup(
|
||||
parent: self,
|
||||
updatePlayerState: updatePlayerState,
|
||||
updateVideoProgress: updateVideoProgress
|
||||
)
|
||||
}
|
||||
|
||||
// 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 !initialSeekPerformed,
|
||||
startPosition > 0,
|
||||
shouldPerformInitialSeek,
|
||||
vlc.player.isSeekable else { return }
|
||||
|
||||
initialSeekPerformed = true
|
||||
logger.debug("First time update, performing initial seek to \(self.startPosition) seconds")
|
||||
vlc.player.time = VLCTime(int: startPosition * 1000)
|
||||
}
|
||||
|
||||
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() {
|
||||
self.vlc.pipController?.stateChangeEventHandler = { (isStarted: Bool) in
|
||||
self.onPipStarted?(["pipStarted": isStarted])
|
||||
}
|
||||
self.vlc.pipController?.startPictureInPicture()
|
||||
}
|
||||
|
||||
@objc func play() {
|
||||
self.vlc.player.play()
|
||||
self.isPaused = false
|
||||
logger.debug("Play")
|
||||
}
|
||||
|
||||
@objc func pause() {
|
||||
self.vlc.player.pause()
|
||||
self.isPaused = true
|
||||
}
|
||||
|
||||
@objc func seekTo(_ time: Int32) {
|
||||
let wasPlaying = vlc.player.isPlaying
|
||||
if wasPlaying {
|
||||
self.pause()
|
||||
}
|
||||
|
||||
if let duration = vlc.player.media?.length.intValue {
|
||||
logger.debug("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
|
||||
vlc.player.time = VLCTime(int: seekTime)
|
||||
self.updatePlayerState()
|
||||
|
||||
// Let mediaPlayerStateChanged handle play state change
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
if wasPlaying {
|
||||
self.play()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.error("Unable to retrieve video duration")
|
||||
}
|
||||
}
|
||||
|
||||
@objc func setSource(_ source: [String: Any]) {
|
||||
logger.debug("Setting source...")
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
if self.hasSource {
|
||||
return
|
||||
}
|
||||
|
||||
var mediaOptions = source["mediaOptions"] as? [String: Any] ?? [:]
|
||||
self.externalTrack = source["externalTrack"] as? [String: String]
|
||||
let initOptions: [String] = source["initOptions"] as? [String] ?? []
|
||||
self.startPosition = source["startPosition"] as? Int32 ?? 0
|
||||
self.externalSubtitles = source["externalSubtitles"] as? [[String: String]]
|
||||
|
||||
for item in initOptions {
|
||||
let option = item.components(separatedBy: "=")
|
||||
mediaOptions.updateValue(
|
||||
option[1], forKey: option[0].replacingOccurrences(of: "--", with: ""))
|
||||
}
|
||||
|
||||
guard let uri = source["uri"] as? String, !uri.isEmpty else {
|
||||
logger.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
|
||||
|
||||
// Set shouldPeformIntial based on isTranscoding and is not a network stream
|
||||
self.shouldPerformInitialSeek = uri.contains("m3u8") || !isNetwork
|
||||
self.onVideoLoadStart?(["target": self.reactTag ?? NSNull()])
|
||||
|
||||
let media: VLCMedia!
|
||||
if isNetwork {
|
||||
logger.debug("Loading network file: \(uri)")
|
||||
media = VLCMedia(url: URL(string: uri)!)
|
||||
} else {
|
||||
logger.debug("Loading local file: \(uri)")
|
||||
if uri.starts(with: "file://"), let url = URL(string: uri) {
|
||||
media = VLCMedia(url: url)
|
||||
} else {
|
||||
media = VLCMedia(path: uri)
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug("Media options: \(mediaOptions)")
|
||||
media.addOptions(mediaOptions)
|
||||
|
||||
self.vlc.player.media = media
|
||||
self.setInitialExternalSubtitles()
|
||||
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.shouldPerformInitialSeek {
|
||||
self.vlc.player.time = VLCTime(number: NSNumber(value: self.startPosition * 1000))
|
||||
}
|
||||
self.play()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func setAudioTrack(_ trackIndex: Int) {
|
||||
print("Setting audio track: \(trackIndex)")
|
||||
let track = self.vlc.player.audioTracks[trackIndex]
|
||||
track.isSelectedExclusively = true
|
||||
}
|
||||
|
||||
@objc func getAudioTracks() -> [[String: Any]]? {
|
||||
return vlc.player.audioTracks.enumerated().map {
|
||||
return ["name": $1.trackName, "index": $0]
|
||||
}
|
||||
}
|
||||
|
||||
@objc func setSubtitleTrack(_ trackIndex: Int) {
|
||||
logger.debug("Attempting to set subtitle track to index: \(trackIndex)")
|
||||
if trackIndex == -1 {
|
||||
logger.debug("Disabling all subtitles")
|
||||
for track in self.vlc.player.textTracks {
|
||||
track.isSelected = false
|
||||
}
|
||||
return
|
||||
}
|
||||
let track = self.vlc.player.textTracks[trackIndex]
|
||||
track.isSelectedExclusively = true;
|
||||
logger.debug("Current subtitle track index after setting: \(track.trackName)")
|
||||
}
|
||||
|
||||
@objc func setSubtitleURL(_ subtitleURL: String, name: String) {
|
||||
guard let url = URL(string: subtitleURL) else {
|
||||
logger.error("Invalid subtitle URL")
|
||||
return
|
||||
}
|
||||
let result = self.vlc.player.addPlaybackSlave(url, type: .subtitle, enforce: false)
|
||||
if result == 0 {
|
||||
let internalName = "Track \(self.customSubtitles.count)"
|
||||
self.customSubtitles.append((internalName: internalName, originalName: name))
|
||||
logger.debug("Subtitle added with result: \(result) \(internalName)")
|
||||
} else {
|
||||
logger.debug("Failed to add subtitle")
|
||||
}
|
||||
}
|
||||
|
||||
@objc func getSubtitleTracks() -> [[String: Any]]? {
|
||||
if self.vlc.player.textTracks.count == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
logger.debug("Number of subtitle tracks: \(self.vlc.player.textTracks.count)")
|
||||
|
||||
let tracks = self.vlc.player.textTracks.enumerated().map { (index, track) in
|
||||
if let customSubtitle = customSubtitles.first(where: {
|
||||
$0.internalName == track.trackName
|
||||
}) {
|
||||
return ["name": customSubtitle.originalName, "index": index]
|
||||
} else {
|
||||
return ["name": track.trackName, "index": index]
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug("Subtitle tracks: \(tracks)")
|
||||
return tracks
|
||||
}
|
||||
|
||||
@objc func stop(completion: (() -> Void)? = nil) {
|
||||
logger.debug("Stopping media...")
|
||||
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 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func performStop(completion: (() -> Void)? = nil) {
|
||||
// Stop the media player
|
||||
vlc.player.stop()
|
||||
|
||||
// Remove observer
|
||||
NotificationCenter.default.removeObserver(self)
|
||||
|
||||
// Clear the video view
|
||||
vlc.getPlayerView().removeFromSuperview()
|
||||
|
||||
isStopping = false
|
||||
completion?()
|
||||
}
|
||||
|
||||
private func updateVideoProgress() {
|
||||
guard self.vlc.player.media != nil else { return }
|
||||
|
||||
let currentTimeMs = self.vlc.player.time.intValue
|
||||
let durationMs = self.vlc.player.media?.length.intValue ?? 0
|
||||
|
||||
logger.debug("Current time: \(currentTimeMs)")
|
||||
self.onVideoProgress?([
|
||||
"currentTime": currentTimeMs,
|
||||
"duration": durationMs,
|
||||
])
|
||||
}
|
||||
|
||||
private func updatePlayerState() {
|
||||
let player = self.vlc.player
|
||||
if player.isPlaying {
|
||||
performInitialSeek()
|
||||
}
|
||||
self.onVideoStateChange?([
|
||||
"target": self.reactTag ?? NSNull(),
|
||||
"currentTime": player.time.intValue,
|
||||
"duration": player.media?.length.intValue ?? 0,
|
||||
"error": false,
|
||||
"isPlaying": player.isPlaying,
|
||||
"isBuffering": !player.isPlaying && player.state == VLCMediaPlayerState.buffering,
|
||||
"state": player.state.description,
|
||||
])
|
||||
}
|
||||
|
||||
// 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 {
|
||||
logger.debug("Deinitialization")
|
||||
performStop()
|
||||
VLCManager.shared.listeners.removeAll()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - SimpleAppLifecycleListener
|
||||
extension VlcPlayer4View: SimpleAppLifecycleListener {
|
||||
func applicationDidEnterBackground() {
|
||||
logger.debug("Entering background")
|
||||
}
|
||||
|
||||
func applicationDidEnterForeground() {
|
||||
logger.debug("Entering foreground, is player visible? \(self.vlc.getPlayerView().superview != nil)")
|
||||
if !self.vlc.getPlayerView().isDescendant(of: self) {
|
||||
logger.debug("Player view is missing. Adding back as subview")
|
||||
self.addSubview(self.vlc.getPlayerView())
|
||||
}
|
||||
|
||||
// Current solution to fixing black screen when re-entering application
|
||||
if let videoTrack = self.vlc.player.videoTracks.first(where: { $0.isSelected == true }),
|
||||
!self.vlc.isMediaPlaying()
|
||||
{
|
||||
videoTrack.isSelected = false
|
||||
videoTrack.isSelectedExclusively = true
|
||||
self.vlc.player.play()
|
||||
self.vlc.player.pause()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 .error: return "Error"
|
||||
case .stopping: return "Stopping"
|
||||
@unknown default: return "Unknown"
|
||||
}
|
||||
}
|
||||
}
|
||||
5
modules/vlc-player-4/src/VlcPlayer4Module.ts
Normal file
5
modules/vlc-player-4/src/VlcPlayer4Module.ts
Normal file
@@ -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("VlcPlayer4");
|
||||
Reference in New Issue
Block a user