diff --git a/Shared/Extensions/JellyfinAPI/JellyfinClient.swift b/Shared/Extensions/JellyfinAPI/JellyfinClient.swift index 67462edaf..f38da433e 100644 --- a/Shared/Extensions/JellyfinAPI/JellyfinClient.swift +++ b/Shared/Extensions/JellyfinAPI/JellyfinClient.swift @@ -12,8 +12,10 @@ import JellyfinAPI extension JellyfinClient { - func fullURL(with request: Request) -> URL { - let fullPath = configuration.url.appendingPathComponent(request.url?.path ?? "") + func fullURL(with request: Request) -> URL? { + + guard let path = request.url?.path else { return configuration.url } + guard let fullPath = fullURL(with: path) else { return nil } var components = URLComponents(string: fullPath.absoluteString)! components.queryItems = request.query?.map { URLQueryItem(name: $0.0, value: $0.1) } ?? [] @@ -21,7 +23,11 @@ extension JellyfinClient { return components.url ?? fullPath } - func fullURL(with path: String) -> URL { - URL(string: configuration.url.absoluteString + path)! + /// Appends the path to the current configuration `URL`, assuming that the path begins with a leading `/`. + /// Returns `nil` if the new `URL` is malformed. + func fullURL(with path: String) -> URL? { + guard let fullPath = URL(string: configuration.url.absoluteString.trimmingCharacters(in: ["/"]) + path) + else { return nil } + return fullPath } } diff --git a/Shared/Extensions/JellyfinAPI/MediaSourceInfo+ItemVideoPlayerViewModel.swift b/Shared/Extensions/JellyfinAPI/MediaSourceInfo/MediaSourceInfo+ItemVideoPlayerViewModel.swift similarity index 83% rename from Shared/Extensions/JellyfinAPI/MediaSourceInfo+ItemVideoPlayerViewModel.swift rename to Shared/Extensions/JellyfinAPI/MediaSourceInfo/MediaSourceInfo+ItemVideoPlayerViewModel.swift index b5da78539..757a29111 100644 --- a/Shared/Extensions/JellyfinAPI/MediaSourceInfo+ItemVideoPlayerViewModel.swift +++ b/Shared/Extensions/JellyfinAPI/MediaSourceInfo/MediaSourceInfo+ItemVideoPlayerViewModel.swift @@ -12,6 +12,8 @@ import Foundation import JellyfinAPI import UIKit +// TODO: strongly type errors + extension MediaSourceInfo { func videoPlayerViewModel(with item: BaseItemDto, playSessionID: String) throws -> VideoPlayerViewModel { @@ -21,8 +23,8 @@ extension MediaSourceInfo { let streamType: StreamType if let transcodingURL, !Defaults[.Experimental.forceDirectPlay] { - guard let fullTranscodeURL = URL(string: transcodingURL, relativeTo: userSession.server.currentURL) - else { throw JellyfinAPIError("Unable to construct transcoded url") } + guard let fullTranscodeURL = userSession.client.fullURL(with: transcodingURL) + else { throw JellyfinAPIError("Unable to make transcode URL") } playbackURL = fullTranscodeURL streamType = .transcode } else { @@ -39,7 +41,10 @@ extension MediaSourceInfo { parameters: videoStreamParameters ) - playbackURL = userSession.client.fullURL(with: videoStreamRequest) + guard let streamURL = userSession.client.fullURL(with: videoStreamRequest) + else { throw JellyfinAPIError("Unable to make stream URL") } + + playbackURL = streamURL streamType = .direct } diff --git a/Shared/Extensions/JellyfinAPI/MediaSourceInfo.swift b/Shared/Extensions/JellyfinAPI/MediaSourceInfo/MediaSourceInfo.swift similarity index 100% rename from Shared/Extensions/JellyfinAPI/MediaSourceInfo.swift rename to Shared/Extensions/JellyfinAPI/MediaSourceInfo/MediaSourceInfo.swift diff --git a/Shared/Extensions/JellyfinAPI/MediaStream.swift b/Shared/Extensions/JellyfinAPI/MediaStream.swift index f6c6aeb94..b2405d1d5 100644 --- a/Shared/Extensions/JellyfinAPI/MediaStream.swift +++ b/Shared/Extensions/JellyfinAPI/MediaStream.swift @@ -20,7 +20,7 @@ extension MediaStream { let client = Container.userSession().client let deliveryPath = deliveryURL.removingFirst(if: client.configuration.url.absoluteString.last == "/") - let fullURL = client.fullURL(with: deliveryPath) + guard let fullURL = client.fullURL(with: deliveryPath) else { return nil } return .init( url: fullURL, diff --git a/Shared/ViewModels/VideoPlayerViewModel.swift b/Shared/ViewModels/VideoPlayerViewModel.swift index c9a51e81c..a9c7d594d 100644 --- a/Shared/ViewModels/VideoPlayerViewModel.swift +++ b/Shared/ViewModels/VideoPlayerViewModel.swift @@ -57,7 +57,8 @@ class VideoPlayerViewModel: ViewModel { parameters: parameters ) - let hlsStreamComponents = URLComponents(url: userSession.client.fullURL(with: request), resolvingAgainstBaseURL: false)! + // TODO: don't force unwrap + let hlsStreamComponents = URLComponents(url: userSession.client.fullURL(with: request)!, resolvingAgainstBaseURL: false)! .addingQueryItem(key: "api_key", value: userSession.user.accessToken) return hlsStreamComponents.url! diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index db029f744..b1a5699b3 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -2535,8 +2535,7 @@ E148128728C154BF003B8787 /* ItemFilter+ItemTrait.swift */, E11B1B6B2718CD68006DA3E8 /* JellyfinAPIError.swift */, E12A9EF729499E0100731C3A /* JellyfinClient.swift */, - E1D8428E2933F2D900D1041A /* MediaSourceInfo.swift */, - E18A8E7F28D6083700333B9A /* MediaSourceInfo+ItemVideoPlayerViewModel.swift */, + E1F5F9B12BA0200500BA5014 /* MediaSourceInfo */, E122A9122788EAAD0060FA63 /* MediaStream.swift */, E1AD105E26D9ADDD003E4A08 /* NameGuidPair.swift */, E148128428C15472003B8787 /* SortOrder.swift */, @@ -2821,6 +2820,15 @@ path = PagingLibraryView; sourceTree = ""; }; + E1F5F9B12BA0200500BA5014 /* MediaSourceInfo */ = { + isa = PBXGroup; + children = ( + E1D8428E2933F2D900D1041A /* MediaSourceInfo.swift */, + E18A8E7F28D6083700333B9A /* MediaSourceInfo+ItemVideoPlayerViewModel.swift */, + ); + path = MediaSourceInfo; + sourceTree = ""; + }; E1FA891C289A302600176FEB /* CollectionItemView */ = { isa = PBXGroup; children = (