Compare commits

...

46 Commits

Author SHA1 Message Date
Gauvain
a02335093b Merge branch 'develop' into fix/vlc4 2025-08-10 23:08:38 +02:00
Gauvain
3b926f639c Merge branch 'develop' into fix/vlc4 2025-07-21 14:10:33 +02:00
Fredrik Burmester
ec1d5a4b23 chore 2025-07-15 08:36:53 +02:00
Alex Kim
c010e73097 Fix playback not working for offline content 2025-07-15 00:44:06 +10:00
Alex Kim
270c12c2f2 Add seekable controls back to pip 2025-07-14 20:50:03 +10:00
Alex Kim
f88771acda Merge 2025-07-14 20:42:51 +10:00
Alex Kim
0da89bd6f3 Merge branch 'develop' into fix/vlc4 2025-07-13 19:39:37 +10:00
Alex Kim
501b88a71e Removing seeking functionality 2025-07-13 16:55:49 +10:00
Alex Kim
c71c7e38e1 Merge branch 'develop' into fix/vlc4 2025-07-13 03:00:54 +10:00
Alex Kim
b6eb8249b0 Go back to vlc4 2025-07-13 02:59:45 +10:00
Alex
ebe36774b0 Change to ts (#848)
Co-authored-by: Alex Kim <alexkim@Alexs-MacBook-Pro.local>
2025-07-13 02:59:45 +10:00
Alex
8f943786af Update package json from expo doctor update (#846)
Co-authored-by: Alex Kim <alexkim@Alexs-MacBook-Pro.local>
2025-07-13 02:59:45 +10:00
Fredrik Burmester
a40dfd0d6f chore: remove unnessesary file 2025-07-13 02:59:45 +10:00
Fredrik Burmester
bcd54718c7 chore: version 2025-07-13 02:59:45 +10:00
Fredrik Burmester
3e74bfdeee chore: version 2025-07-13 02:59:45 +10:00
Fredrik Burmester
b96ca1702f chore 2025-07-13 02:59:45 +10:00
Fredrik Burmester
0e8704e9b5 feat: add CodeRabbit configuration for React Native project 2025-07-13 02:59:44 +10:00
Alex
e247438628 Fix orientation race condition (#841)
Co-authored-by: Alex Kim <alexkim@Alexs-MacBook-Pro.local>
2025-07-13 02:59:44 +10:00
arch-fan
bd073ec574 fix: expo issue by updating deps (#823) 2025-07-13 02:59:44 +10:00
renovate[bot]
5a38e29854 chore(deps): update github/codeql-action action to v3.29.2 (#821)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-13 02:59:44 +10:00
renovate[bot]
5d2ce263e2 fix(deps): update dependency com.android.tools.build:gradle to v8.11.0 (#819) 2025-07-13 02:59:44 +10:00
renovate[bot]
e2c8ed7cbe chore(deps): update github/codeql-action action to v3.29.1 (#818) 2025-07-13 02:59:44 +10:00
renovate[bot]
54beb63adc fix(deps): update dependency react-native-safe-area-context to v5.5.0 (#774)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-13 02:59:44 +10:00
renovate[bot]
8b0c1081ed chore(deps): update dependency @react-native-community/cli to v18 (#783)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-13 02:59:44 +10:00
renovate[bot]
924eb6695c fix(deps): update dependency @shopify/flash-list to v1.8.3 (#736)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-13 02:59:44 +10:00
renovate[bot]
bb7f708a68 chore(deps): update dependency @biomejs/biome to v2 (#811)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Gauvino <uruknarb20@gmail.com>
2025-07-13 02:59:44 +10:00
renovate[bot]
436868cac1 chore(deps): update dependency @types/jest to v30 (#812)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-13 02:59:44 +10:00
renovate[bot]
fb3a105bdf chore(deps): update marocchino/sticky-pull-request-comment action to v2.9.3 (#810)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-13 02:59:44 +10:00
Chris
8ee7c57606 docs: Update README.md (#802) 2025-07-13 02:59:44 +10:00
Chris
3e5d5aad9f docs: Clarify legal use of Streamyfin with a piracy disclaimer in README (#801) 2025-07-13 02:59:44 +10:00
renovate[bot]
d9dc2e089a fix(deps): update dependency i18next to v25 (#784) 2025-07-13 02:59:44 +10:00
renovate[bot]
edc3c633f3 fix(deps): update dependency com.android.tools.build:gradle to v8 (#772) 2025-07-13 02:59:44 +10:00
renovate[bot]
afc96cde05 chore(deps): update dependency lint-staged to v16 (#771)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-13 02:59:44 +10:00
Gauvain
4f75cf64dc fix: remove pull request target 2025-07-13 02:59:44 +10:00
renovate[bot]
f0f2bd34ba chore(deps): update github/codeql-action action to v3.29.0 (#769)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-13 02:59:44 +10:00
Gauvain
64b353e683 fix: pr build 2025-07-13 02:59:44 +10:00
renovate[bot]
d6c242d0d5 chore(deps): update dependency node to v22 (#766)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-13 02:59:44 +10:00
renovate[bot]
ab16972921 chore(deps): update github/codeql-action action to v3.28.19 (#763)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-13 02:59:44 +10:00
Gauvain
ef4bb14216 fix: add dashboard for renovate 2025-07-13 02:59:44 +10:00
storm1er
d48398589d feat: Persist ignore safe area accross stream and app restart (#701) 2025-07-13 02:59:44 +10:00
Gauvino
2133b382a1 fix: remove git commit from release sonce it's already present in artifact menu 2025-07-13 02:59:44 +10:00
Gauvino
8c5b9d068d fix: put @main instead of v8 to fix cache problem 2025-07-13 02:59:44 +10:00
Gauvain
26225bbf52 feat: update bun version (#745) 2025-07-13 02:59:44 +10:00
Gauvain
0a9da729a1 refactor: fix the ios-build action (#742) 2025-07-13 02:59:44 +10:00
Fredrik Burmester
cad9472779 chore: version 2025-07-13 02:59:44 +10:00
lostb1t
0a72396a16 fix: loading conditionals (#753) 2025-06-07 13:19:32 +02:00
14 changed files with 39 additions and 528 deletions

View File

@@ -562,7 +562,7 @@ export default function page() {
source={{
uri: stream?.url || "",
autoplay: true,
isNetwork: true,
isNetwork: !offline,
startPosition,
externalSubtitles,
initOptions,

View File

@@ -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<VideoProviderProps> = ({
}) => {
const [audioTracks, setAudioTracks] = useState<Track[] | null>(null);
const [subtitleTracks, setSubtitleTracks] = useState<Track[] | null>(null);
const [settings] = useSettings();
const [_settings] = useSettings();
const ControlContext = useControlContext();
const isVideoLoaded = ControlContext?.isVideoLoaded;
@@ -136,7 +136,7 @@ export const VideoProvider: React.FC<VideoProviderProps> = ({
);
// 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

View File

@@ -13,22 +13,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<NativeViewRef, VlcPlayerViewProps>(
(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 <VLC3ViewManager {...props} ref={ref} />;
}
}
console.log("Using default Vlc Player");
return <VLCViewManager {...props} ref={ref} />;
},
(props, ref) => <VLCViewManager {...props} ref={ref} />,
);
const VlcPlayerView = React.forwardRef<VlcPlayerViewRef, VlcPlayerViewProps>(

View File

@@ -1,6 +0,0 @@
{
"platforms": ["ios", "tvos"],
"ios": {
"modules": ["VlcPlayer3Module"]
}
}

View File

@@ -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

View File

@@ -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)
}
}
}
}

View File

@@ -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"
}
}
}

View File

@@ -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");

View File

@@ -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 = ''

View File

@@ -68,4 +68,4 @@ public class VlcPlayerModule: Module {
}
}
}
}
}

View File

@@ -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.shouldPerformInitialSeek,
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,15 @@ 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
// 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) {
@@ -254,6 +270,8 @@ class VlcPlayerView: ExpoView {
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!
@@ -277,8 +295,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.shouldPerformInitialSeek {
self.vlc.player.time = VLCTime(number: NSNumber(value: self.startPosition * 1000))
}
self.play()
self.vlc.player.time = VLCTime(number: NSNumber(value: self.startPosition * 1000))
}
}
}

View File

@@ -128,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,
}

View File

@@ -31,7 +31,7 @@ export const getStreamUrl = async ({
subtitleStreamIndex?: number;
height?: number;
mediaSourceId?: string | null;
download?: bool;
download?: boolean;
deviceId?: string | null;
}): Promise<{
url: string | null;
@@ -71,8 +71,8 @@ export const getStreamUrl = async ({
}
sessionId = res.data.PlaySessionId || null;
mediaSource = res.data.MediaSources[0];
let transcodeUrl = mediaSource.TranscodingUrl;
mediaSource = res.data.MediaSources?.[0];
let transcodeUrl = mediaSource?.TranscodingUrl;
if (transcodeUrl) {
if (download) {
@@ -123,7 +123,7 @@ export const getStreamUrl = async ({
return {
url: directPlayUrl,
sessionId: sessionId || playSessionId,
sessionId: sessionId || playSessionId || null,
mediaSource,
};
};

View File

@@ -44,7 +44,7 @@ export const generateDeviceProfile = async () => {
DirectPlayProfiles: [
{
Type: MediaTypes.Video,
Container: "mp4,mkv,avi,mov,flv,ts,m2ts,webm,ogv,3gp,hls",
Container: "mkv,avi,mov,flv,ts,m2ts,webm,ogv,3gp,hls",
VideoCodec:
"h264,hevc,mpeg4,divx,xvid,wmv,vc1,vp8,vp9,av1,avi,mpeg,mpeg2video",
AudioCodec: "aac,ac3,eac3,mp3,flac,alac,opus,vorbis,wma,dts",