Implement Factory (#587)

This commit is contained in:
Ethan Pippin 2022-09-15 11:32:47 -06:00 committed by GitHub
parent fb38394a43
commit 3ffb67a400
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 186 additions and 108 deletions

View File

@ -8,6 +8,7 @@
import Combine
import Defaults
import Factory
import Foundation
import Nuke
import Stinsen
@ -15,6 +16,10 @@ import SwiftUI
import WidgetKit
final class MainCoordinator: NavigationCoordinatable {
@Injected(LogManager.service)
private var logger
var stack: NavigationStack<MainCoordinator>
@Root
@ -60,13 +65,13 @@ final class MainCoordinator: NavigationCoordinatable {
@objc
func didSignIn() {
LogManager.log.info("Received `didSignIn` from SwiftfinNotificationCenter.")
logger.info("Received `didSignIn` from SwiftfinNotificationCenter.")
root(\.mainTab)
}
@objc
func didSignOut() {
LogManager.log.info("Received `didSignOut` from SwiftfinNotificationCenter.")
logger.info("Received `didSignOut` from SwiftfinNotificationCenter.")
root(\.serverList)
}

View File

@ -6,12 +6,17 @@
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
//
import Factory
import Foundation
import Nuke
import Stinsen
import SwiftUI
final class MainCoordinator: NavigationCoordinatable {
@Injected(LogManager.service)
private var logger
var stack = NavigationStack<MainCoordinator>(initial: \MainCoordinator.mainTab)
@Root
@ -40,13 +45,13 @@ final class MainCoordinator: NavigationCoordinatable {
@objc
func didSignIn() {
LogManager.log.info("Received `didSignIn` from NSNotificationCenter.")
logger.info("Received `didSignIn` from NSNotificationCenter.")
root(\.mainTab)
}
@objc
func didSignOut() {
LogManager.log.info("Received `didSignOut` from NSNotificationCenter.")
logger.info("Received `didSignOut` from NSNotificationCenter.")
root(\.serverList)
}

View File

@ -14,7 +14,7 @@ import UIKit
extension BaseItemDto {
func createVideoPlayerViewModel() -> AnyPublisher<[VideoPlayerViewModel], Error> {
LogManager.log.debug("Creating video player view model for item: \(id ?? "")")
LogManager.service().debug("Creating video player view model for item: \(id ?? "")")
let builder = DeviceProfileBuilder()
// TODO: fix bitrate settings
@ -182,7 +182,7 @@ extension BaseItemDto {
func createLiveTVVideoPlayerViewModel() -> AnyPublisher<[VideoPlayerViewModel], Error> {
LogManager.log.debug("Creating liveTV video player view model for item: \(id ?? "")")
LogManager.service().debug("Creating liveTV video player view model for item: \(id ?? "")")
let builder = DeviceProfileBuilder()
// TODO: fix bitrate settings

View File

@ -22,4 +22,8 @@ public extension URL {
return items
}
static var documents: URL {
FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
}
}

View File

@ -6,10 +6,15 @@
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
//
import Factory
import Foundation
import UDPBroadcast
public class ServerDiscovery {
@Injected(LogManager.service)
private var logger
public struct ServerLookupResponse: Codable, Hashable, Identifiable {
public func hash(into hasher: inout Hasher) {
@ -56,7 +61,7 @@ public class ServerDiscovery {
func receiveHandler(_ ipAddress: String, _ port: Int, _ data: Data) {
do {
let response = try JSONDecoder().decode(ServerLookupResponse.self, from: data)
LogManager.log.debug("Received JellyfinServer from \"\(response.name)\"", tag: "ServerDiscovery")
logger.debug("Received JellyfinServer from \"\(response.name)\"", tag: "ServerDiscovery")
completion(response)
} catch {
completion(nil)
@ -64,15 +69,15 @@ public class ServerDiscovery {
}
func errorHandler(error: UDPBroadcastConnection.ConnectionError) {
LogManager.log.error("Error handling response: \(error.localizedDescription)", tag: "ServerDiscovery")
logger.error("Error handling response: \(error.localizedDescription)", tag: "ServerDiscovery")
}
do {
self.connection = try! UDPBroadcastConnection(port: 7359, handler: receiveHandler, errorHandler: errorHandler)
try self.connection?.sendBroadcast("Who is JellyfinServer?")
LogManager.log.debug("Discovery broadcast sent", tag: "ServerDiscovery")
logger.debug("Discovery broadcast sent", tag: "ServerDiscovery")
} catch {
LogManager.log.error("Error sending discovery broadcast", tag: "ServerDiscovery")
logger.error("Error sending discovery broadcast", tag: "ServerDiscovery")
}
}
}

View File

@ -6,16 +6,40 @@
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
//
import Factory
import Foundation
import Puppy
class LogManager {
static let log = Puppy()
static let service = Factory<Puppy>(scope: .singleton) {
Puppy.swiftfinInstance()
}
static func setup() {
// static let log = Puppy()
}
let logsDirectory = getDocumentsDirectory().appendingPathComponent("logs", isDirectory: true)
class LogFormatter: LogFormattable {
func formatMessage(
_ level: LogLevel,
message: String,
tag: String,
function: String,
file: String,
line: UInt,
swiftLogInfo: [String: String],
label: String,
date: Date,
threadID: UInt64
) -> String {
let file = shortFileName(file).replacingOccurrences(of: ".swift", with: "")
return " [\(level.emoji) \(level)] \(file)#\(line):\(function) \(message)"
}
}
private extension Puppy {
static func swiftfinInstance() -> Puppy {
let logsDirectory = URL.documents.appendingPathComponent("logs", isDirectory: true)
do {
try FileManager.default.createDirectory(
@ -38,33 +62,9 @@ class LogManager {
let consoleLogger = ConsoleLogger("org.jellyfin.swiftfin.logger.console")
consoleLogger.format = LogFormatter()
log.add(fileRotationLogger, withLevel: .debug)
log.add(consoleLogger, withLevel: .debug)
}
private static func getDocumentsDirectory() -> URL {
// find all possible documents directories for this user
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
// just send back the first one, which ought to be the only one
return paths[0]
}
}
class LogFormatter: LogFormattable {
func formatMessage(
_ level: LogLevel,
message: String,
tag: String,
function: String,
file: String,
line: UInt,
swiftLogInfo: [String: String],
label: String,
date: Date,
threadID: UInt64
) -> String {
let file = shortFileName(file).replacingOccurrences(of: ".swift", with: "")
return " [\(level.emoji) \(level)] \(file)#\(line):\(function) \(message)"
let logger = Puppy()
logger.add(fileRotationLogger, withLevel: .debug)
logger.add(consoleLogger, withLevel: .debug)
return logger
}
}

View File

@ -6,10 +6,14 @@
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
//
import Factory
import Foundation
class SwiftfinNotification {
@Injected(Notifications.service)
private var notificationService
private let notificationName: Notification.Name
fileprivate init(_ notificationName: Notification.Name) {
@ -17,23 +21,21 @@ class SwiftfinNotification {
}
func post(object: Any? = nil) {
Notifications.main.post(name: notificationName, object: object)
notificationService.post(name: notificationName, object: object)
}
func subscribe(_ observer: Any, selector: Selector) {
Notifications.main.addObserver(observer, selector: selector, name: notificationName, object: nil)
notificationService.addObserver(observer, selector: selector, name: notificationName, object: nil)
}
func unsubscribe(_ observer: Any) {
Notifications.main.removeObserver(self, name: notificationName, object: nil)
notificationService.removeObserver(self, name: notificationName, object: nil)
}
}
enum Notifications {
static let main: NotificationCenter = {
NotificationCenter()
}()
static let service = Factory(scope: .singleton) { NotificationCenter() }
final class Key {
public typealias NotificationKey = Notifications.Key
@ -50,10 +52,6 @@ enum Notifications {
static subscript(key: Key) -> SwiftfinNotification {
key.underlyingNotification
}
static func unsubscribe(_ observer: Any) {
main.removeObserver(observer)
}
}
extension Notifications.Key {
@ -63,8 +61,5 @@ extension Notifications.Key {
static let processDeepLink = NotificationKey("processDeepLink")
static let didPurge = NotificationKey("didPurge")
static let didChangeServerCurrentURI = NotificationKey("didChangeCurrentLoginURI")
static let toggleOfflineMode = NotificationKey("toggleOfflineMode")
static let didDeleteOfflineItem = NotificationKey("didDeleteOfflineItem")
static let didAddDownload = NotificationKey("didAddDownload")
static let didSendStopReport = NotificationKey("didSendStopReport")
}

View File

@ -7,6 +7,7 @@
//
import Combine
import Factory
import Foundation
import JellyfinAPI
import Stinsen
@ -49,7 +50,7 @@ final class ConnectToServerViewModel: ViewModel {
let uri = uri.trimmingCharacters(in: .whitespacesAndNewlines)
.trimmingCharacters(in: .objectReplacement)
LogManager.log.debug("Attempting to connect to server at \"\(uri)\"", tag: "connectToServer")
logger.debug("Attempting to connect to server at \"\(uri)\"", tag: "connectToServer")
SessionManager.main.connectToServer(with: uri)
.trackActivity(loading)
.sink(receiveCompletion: { completion in
@ -92,7 +93,7 @@ final class ConnectToServerViewModel: ViewModel {
}
}
}, receiveValue: { server in
LogManager.log.debug("Connected to server at \"\(uri)\"", tag: "connectToServer")
self.logger.debug("Connected to server at \"\(uri)\"", tag: "connectToServer")
self.router?.route(to: \.userSignIn, server)
})
.store(in: &cancellables)

View File

@ -59,7 +59,7 @@ final class HomeViewModel: ViewModel {
@objc
func refresh() {
LogManager.log.debug("Refresh called.")
logger.debug("Refresh called.")
refreshLibrariesLatest()
refreshLatestAddedItems()
@ -85,7 +85,7 @@ final class HomeViewModel: ViewModel {
var newLibraries: [BaseItemDto] = []
response.items!.forEach { item in
LogManager.log
self.logger
.debug("Retrieved user view: \(item.id!) (\(item.name ?? "nil")) with type \(item.collectionType ?? "nil")")
if item.collectionType == "movies" || item.collectionType == "tvshows" {
newLibraries.append(item)
@ -165,7 +165,7 @@ final class HomeViewModel: ViewModel {
self.handleAPIRequestError(completion: completion)
}
}, receiveValue: { response in
LogManager.log.debug("Retrieved \(String(response.items!.count)) resume items")
self.logger.debug("Retrieved \(String(response.items!.count)) resume items")
self.resumeItems = response.items ?? []
})

View File

@ -7,6 +7,7 @@
//
import Combine
import Factory
import Foundation
import JellyfinAPI
import UIKit
@ -65,7 +66,7 @@ class ItemViewModel: ViewModel {
} else {
// Remove if necessary. Note that this cannot be in deinit as
// holding as an observer won't allow the object to be deinit-ed
Notifications.unsubscribe(self)
Notifications[.didSendStopReport].unsubscribe(self)
}
}

View File

@ -39,7 +39,7 @@ final class SeasonItemViewModel: ItemViewModel, EpisodesRowManager {
}
private func requestEpisodes() {
LogManager.log
logger
.debug("Getting episodes in season \(item.id!) (\(item.name!)) of show \(item.seriesId!) (\(item.seriesName!))")
TvShowsAPI.getEpisodes(
seriesId: item.seriesId ?? "",
@ -78,7 +78,7 @@ final class SeasonItemViewModel: ItemViewModel, EpisodesRowManager {
!episode.unaired && !episode.missing && episode.seasonId ?? "" == self.item.id!
}) {
self.playButtonItem = nextUpItem
LogManager.log.debug("Nextup in season \(self.item.id!) (\(self.item.name!)): \(nextUpItem.id!)")
self.logger.debug("Nextup in season \(self.item.id!) (\(self.item.name!)): \(nextUpItem.id!)")
}
// if self.playButtonItem == nil && !self.episodes.isEmpty {

View File

@ -49,7 +49,7 @@ final class SeriesItemViewModel: ItemViewModel, EpisodesRowManager {
}
private func getNextUp() {
LogManager.log.debug("Getting next up for show \(self.item.id!) (\(self.item.name!))")
logger.debug("Getting next up for show \(self.item.id!) (\(self.item.name!))")
TvShowsAPI.getNextUp(
userId: SessionManager.main.currentLogin.user.id,
seriesId: self.item.id!,

View File

@ -6,6 +6,7 @@
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
//
import Factory
import Foundation
import JellyfinAPI
import SwiftUICollection
@ -69,7 +70,7 @@ final class LiveTVChannelsViewModel: ViewModel {
.sink(receiveCompletion: { [weak self] completion in
self?.handleAPIRequestError(completion: completion)
}, receiveValue: { [weak self] _ in
LogManager.log.debug("Received Guide Info")
self?.logger.debug("Received Guide Info")
guard let self = self else { return }
self.getChannels()
})
@ -89,7 +90,7 @@ final class LiveTVChannelsViewModel: ViewModel {
.sink(receiveCompletion: { [weak self] completion in
self?.handleAPIRequestError(completion: completion)
}, receiveValue: { [weak self] response in
LogManager.log.debug("Received \(response.items?.count ?? 0) Channels")
self?.logger.debug("Received \(response.items?.count ?? 0) Channels")
guard let self = self else { return }
self.channels = response.items ?? []
self.getPrograms()
@ -100,7 +101,7 @@ final class LiveTVChannelsViewModel: ViewModel {
private func getPrograms() {
// http://192.168.1.50:8096/LiveTv/Programs
guard !channels.isEmpty else {
LogManager.log.debug("Cannot get programs, channels list empty. ")
logger.debug("Cannot get programs, channels list empty. ")
return
}
let channelIds = channels.compactMap(\.id)
@ -126,7 +127,7 @@ final class LiveTVChannelsViewModel: ViewModel {
.sink(receiveCompletion: { [weak self] completion in
self?.handleAPIRequestError(completion: completion)
}, receiveValue: { [weak self] response in
LogManager.log.debug("Received \(response.items?.count ?? 0) Programs")
self?.logger.debug("Received \(response.items?.count ?? 0) Programs")
guard let self = self else { return }
self.programs = response.items ?? []
self.channelPrograms = self.processChannelPrograms()
@ -178,7 +179,7 @@ final class LiveTVChannelsViewModel: ViewModel {
}
timer = Timer(fire: nextMinute, interval: 60 * 10, repeats: true) { [weak self] _ in
guard let self = self else { return }
LogManager.log.debug("LiveTVChannels schedule check...")
self.logger.debug("LiveTVChannels schedule check...")
DispatchQueue.global(qos: .background).async {
let newChanPrgs = self.processChannelPrograms()
DispatchQueue.main.async {

View File

@ -49,7 +49,7 @@ final class LiveTVProgramsViewModel: ViewModel {
.sink(receiveCompletion: { [weak self] completion in
self?.handleAPIRequestError(completion: completion)
}, receiveValue: { [weak self] response in
LogManager.log.debug("Received \(response.items?.count ?? 0) Channels")
self?.logger.debug("Received \(response.items?.count ?? 0) Channels")
guard let self = self else { return }
if let chans = response.items {
for chan in chans {
@ -82,7 +82,7 @@ final class LiveTVProgramsViewModel: ViewModel {
.sink(receiveCompletion: { [weak self] completion in
self?.handleAPIRequestError(completion: completion)
}, receiveValue: { [weak self] response in
LogManager.log.debug("Received \(String(response.items?.count ?? 0)) Recommended Programs")
self?.logger.debug("Received \(String(response.items?.count ?? 0)) Recommended Programs")
guard let self = self else { return }
self.recommendedItems = response.items ?? []
})
@ -109,7 +109,7 @@ final class LiveTVProgramsViewModel: ViewModel {
.sink(receiveCompletion: { [weak self] completion in
self?.handleAPIRequestError(completion: completion)
}, receiveValue: { [weak self] response in
LogManager.log.debug("Received \(String(response.items?.count ?? 0)) Series Items")
self?.logger.debug("Received \(String(response.items?.count ?? 0)) Series Items")
guard let self = self else { return }
self.seriesItems = response.items ?? []
})
@ -136,7 +136,7 @@ final class LiveTVProgramsViewModel: ViewModel {
.sink(receiveCompletion: { [weak self] completion in
self?.handleAPIRequestError(completion: completion)
}, receiveValue: { [weak self] response in
LogManager.log.debug("Received \(String(response.items?.count ?? 0)) Movie Items")
self?.logger.debug("Received \(String(response.items?.count ?? 0)) Movie Items")
guard let self = self else { return }
self.movieItems = response.items ?? []
})
@ -159,7 +159,7 @@ final class LiveTVProgramsViewModel: ViewModel {
.sink(receiveCompletion: { [weak self] completion in
self?.handleAPIRequestError(completion: completion)
}, receiveValue: { [weak self] response in
LogManager.log.debug("Received \(String(response.items?.count ?? 0)) Sports Items")
self?.logger.debug("Received \(String(response.items?.count ?? 0)) Sports Items")
guard let self = self else { return }
self.sportsItems = response.items ?? []
})
@ -182,7 +182,7 @@ final class LiveTVProgramsViewModel: ViewModel {
.sink(receiveCompletion: { [weak self] completion in
self?.handleAPIRequestError(completion: completion)
}, receiveValue: { [weak self] response in
LogManager.log.debug("Received \(String(response.items?.count ?? 0)) Kids Items")
self?.logger.debug("Received \(String(response.items?.count ?? 0)) Kids Items")
guard let self = self else { return }
self.kidsItems = response.items ?? []
})
@ -205,7 +205,7 @@ final class LiveTVProgramsViewModel: ViewModel {
.sink(receiveCompletion: { [weak self] completion in
self?.handleAPIRequestError(completion: completion)
}, receiveValue: { [weak self] response in
LogManager.log.debug("Received \(String(response.items?.count ?? 0)) News Items")
self?.logger.debug("Received \(String(response.items?.count ?? 0)) News Items")
guard let self = self else { return }
self.newsItems = response.items ?? []
})

View File

@ -32,13 +32,13 @@ final class QuickConnectSettingsViewModel: ViewModel {
self.handleAPIRequestError(displayMessage: L10n.quickConnectInvalidError, completion: completion)
switch completion {
case .failure:
LogManager.log.debug("Invalid Quick Connect code entered")
self.logger.debug("Invalid Quick Connect code entered")
default:
break
}
}, receiveValue: { _ in
// receiving a successful HTTP response indicates a valid code
LogManager.log.debug("Valid Quick connect code entered")
self.logger.debug("Valid Quick connect code entered")
self.showSuccessMessage = true
})
.store(in: &cancellables)

View File

@ -24,6 +24,7 @@ final class SettingsViewModel: ViewModel {
self.server = server
self.user = user
super.init()
// Bitrates
let url = Bundle.main.url(forResource: "bitrates", withExtension: "json")!
@ -33,10 +34,10 @@ final class SettingsViewModel: ViewModel {
do {
self.bitrates = try JSONDecoder().decode([Bitrates].self, from: jsonData)
} catch {
LogManager.log.error("Error converting processed JSON into Swift compatible schema.")
logger.error("Error converting processed JSON into Swift compatible schema.")
}
} catch {
LogManager.log.error("Error processing JSON file `bitrates.json`")
logger.error("Error processing JSON file `bitrates.json`")
}
// Track languages

View File

@ -46,7 +46,7 @@ final class UserSignInViewModel: ViewModel {
}
func signIn(username: String, password: String) {
LogManager.log.debug("Attempting to login to server at \"\(server.currentURI)\"", tag: "login")
logger.debug("Attempting to login to server at \"\(server.currentURI)\"", tag: "login")
let username = username.trimmingCharacters(in: .whitespacesAndNewlines)
.trimmingCharacters(in: .objectReplacement)
@ -99,7 +99,7 @@ final class UserSignInViewModel: ViewModel {
self.quickConnectSecret = response.secret
self.quickConnectCode = response.code
LogManager.log.debug("QuickConnect code: \(response.code ?? .emptyDash)")
self.logger.debug("QuickConnect code: \(response.code ?? .emptyDash)")
self.quickConnectTimer = RepeatingTimer(interval: 5) {
self.checkAuthStatus(onSuccess)
@ -120,7 +120,7 @@ final class UserSignInViewModel: ViewModel {
// this is a repeated call
}, receiveValue: { value in
guard let authenticated = value.authenticated, authenticated else {
LogManager.log.debug("QuickConnect not authenticated yet")
self.logger.debug("QuickConnect not authenticated yet")
return
}

View File

@ -511,7 +511,7 @@ extension VideoPlayerViewModel {
.sink { completion in
self.handleAPIRequestError(completion: completion)
} receiveValue: { _ in
LogManager.log.debug("Start report sent for item: \(self.item.id ?? "No ID")")
self.logger.debug("Start report sent for item: \(self.item.id ?? "No ID")")
}
.store(in: &cancellables)
}
@ -547,7 +547,7 @@ extension VideoPlayerViewModel {
.sink { completion in
self.handleAPIRequestError(completion: completion)
} receiveValue: { _ in
LogManager.log.debug("Pause report sent for item: \(self.item.id ?? "No ID")")
self.logger.debug("Pause report sent for item: \(self.item.id ?? "No ID")")
}
.store(in: &cancellables)
}
@ -592,7 +592,7 @@ extension VideoPlayerViewModel {
.sink { completion in
self.handleAPIRequestError(completion: completion)
} receiveValue: { _ in
LogManager.log.debug("Playback progress sent for item: \(self.item.id ?? "No ID")")
self.logger.debug("Playback progress sent for item: \(self.item.id ?? "No ID")")
}
.store(in: &cancellables)
@ -619,7 +619,7 @@ extension VideoPlayerViewModel {
.sink { completion in
self.handleAPIRequestError(completion: completion)
} receiveValue: { _ in
LogManager.log.debug("Stop report sent for item: \(self.item.id ?? "No ID")")
self.logger.debug("Stop report sent for item: \(self.item.id ?? "No ID")")
Notifications[.didSendStopReport].post(object: self.item.id)
}
.store(in: &cancellables)

View File

@ -8,11 +8,14 @@
import ActivityIndicator
import Combine
import Factory
import Foundation
import JellyfinAPI
class ViewModel: ObservableObject {
@Injected(LogManager.service)
var logger
@Published
var isLoading = false
@Published
@ -39,18 +42,18 @@ class ViewModel: ObservableObject {
case .error(-1, _, _, _):
networkError = .URLError(response: errorResponse, displayMessage: displayMessage)
// Use the errorResponse description for debugging, rather than the user-facing friendly description which may not be implemented
LogManager.log
logger
.error(
"Request failed: URL request failed with error \(networkError.errorMessage.code): \(errorResponse.localizedDescription)"
)
case .error(-2, _, _, _):
networkError = .HTTPURLError(response: errorResponse, displayMessage: displayMessage)
LogManager.log
logger
.error("Request failed: HTTP URL request failed with description: \(errorResponse.localizedDescription)")
default:
networkError = .JellyfinError(response: errorResponse, displayMessage: displayMessage)
// Able to use user-facing friendly description here since just HTTP status codes
LogManager.log
logger
.error(
"Request failed: \(networkError.errorMessage.code) - \(networkError.errorMessage.title): \(networkError.errorMessage.message)\n\(error.localizedDescription)"
)
@ -66,7 +69,7 @@ class ViewModel: ObservableObject {
message: swiftfinError.errorDescription ?? ""
)
self.errorMessage = errorMessage
LogManager.log.error("Request failed: \(swiftfinError.errorDescription ?? "")")
logger.error("Request failed: \(swiftfinError.errorDescription ?? "")")
default:
let genericErrorMessage = ErrorMessage(
@ -75,7 +78,7 @@ class ViewModel: ObservableObject {
message: error.localizedDescription
)
self.errorMessage = genericErrorMessage
LogManager.log.error("Request failed: Generic error - \(error.localizedDescription)")
logger.error("Request failed: Generic error - \(error.localizedDescription)")
}
}
}

View File

@ -6,12 +6,15 @@
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
//
import Factory
import SwiftUI
extension ItemView {
struct PlayButton: View {
@Injected(LogManager.service)
private var logger
@EnvironmentObject
private var itemRouter: ItemCoordinator.Router
@ObservedObject
@ -24,7 +27,7 @@ extension ItemView {
if let selectedVideoPlayerViewModel = viewModel.selectedVideoPlayerViewModel {
itemRouter.route(to: \.videoPlayer, selectedVideoPlayerViewModel)
} else {
LogManager.log.error("Attempted to play item but no playback information available")
logger.error("Attempted to play item but no playback information available")
}
} label: {
HStack(spacing: 15) {
@ -55,7 +58,7 @@ extension ItemView {
selectedVideoPlayerViewModel.injectCustomValues(startFromBeginning: true)
itemRouter.route(to: \.videoPlayer, selectedVideoPlayerViewModel)
} else {
LogManager.log.error("Attempted to play item but no playback information available")
logger.error("Attempted to play item but no playback information available")
}
} label: {
Label(L10n.playFromBeginning, systemImage: "gobackward")

View File

@ -10,6 +10,7 @@ import AVFoundation
import AVKit
import Combine
import Defaults
import Factory
import JellyfinAPI
import MediaPlayer
import SwiftUI
@ -20,6 +21,9 @@ import UIKit
class LiveTVPlayerViewController: UIViewController {
@Injected(LogManager.service)
private var logger
// MARK: variables
private var viewModel: VideoPlayerViewModel
@ -476,11 +480,11 @@ extension LiveTVPlayerViewController {
viewModel = newViewModel
if viewModel.streamType == .direct {
LogManager.log.debug("Player set up with direct play stream for item: \(viewModel.item.id ?? .emptyDash)")
logger.debug("Player set up with direct play stream for item: \(viewModel.item.id ?? .emptyDash)")
} else if viewModel.streamType == .transcode && Defaults[.Experimental.forceDirectPlay] {
LogManager.log.debug("Player set up with forced direct stream for item: \(viewModel.item.id ?? .emptyDash)")
logger.debug("Player set up with forced direct stream for item: \(viewModel.item.id ?? .emptyDash)")
} else {
LogManager.log.debug("Player set up with transcoded stream for item: \(viewModel.item.id ?? .emptyDash)")
logger.debug("Player set up with transcoded stream for item: \(viewModel.item.id ?? .emptyDash)")
}
}

View File

@ -10,6 +10,7 @@ import AVFoundation
import AVKit
import Combine
import Defaults
import Factory
import JellyfinAPI
import MediaPlayer
import SwiftUI
@ -20,6 +21,9 @@ import UIKit
class VLCPlayerViewController: UIViewController {
@Injected(LogManager.service)
private var logger
// MARK: variables
private var viewModel: VideoPlayerViewModel
@ -476,11 +480,11 @@ extension VLCPlayerViewController {
viewModel = newViewModel
if viewModel.streamType == .direct {
LogManager.log.debug("Player set up with direct play stream for item: \(viewModel.item.id ?? .emptyDash)")
logger.debug("Player set up with direct play stream for item: \(viewModel.item.id ?? .emptyDash)")
} else if viewModel.streamType == .transcode && Defaults[.Experimental.forceDirectPlay] {
LogManager.log.debug("Player set up with forced direct stream for item: \(viewModel.item.id ?? .emptyDash)")
logger.debug("Player set up with forced direct stream for item: \(viewModel.item.id ?? .emptyDash)")
} else {
LogManager.log.debug("Player set up with transcoded stream for item: \(viewModel.item.id ?? .emptyDash)")
logger.debug("Player set up with transcoded stream for item: \(viewModel.item.id ?? .emptyDash)")
}
}

View File

@ -410,6 +410,8 @@
E19169CE272514760085832A /* HTTPScheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = E19169CD272514760085832A /* HTTPScheme.swift */; };
E19169CF272514760085832A /* HTTPScheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = E19169CD272514760085832A /* HTTPScheme.swift */; };
E192608028D28AAD002314B4 /* UserProfileButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E192607F28D28AAD002314B4 /* UserProfileButton.swift */; };
E192608328D2D0DB002314B4 /* Factory in Frameworks */ = {isa = PBXBuildFile; productRef = E192608228D2D0DB002314B4 /* Factory */; };
E192608828D2E5F0002314B4 /* Factory in Frameworks */ = {isa = PBXBuildFile; productRef = E192608728D2E5F0002314B4 /* Factory */; };
E1937A3B288E54AD00CB80AA /* BaseItemDto+Images.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1937A3A288E54AD00CB80AA /* BaseItemDto+Images.swift */; };
E1937A3C288E54AD00CB80AA /* BaseItemDto+Images.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1937A3A288E54AD00CB80AA /* BaseItemDto+Images.swift */; };
E1937A3E288F0D3D00CB80AA /* UIScreenExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1937A3D288F0D3D00CB80AA /* UIScreenExtensions.swift */; };
@ -1027,6 +1029,7 @@
E12186DE2718F1C50010884C /* Defaults in Frameworks */,
E1347DB6279E3CA500BC6161 /* Puppy in Frameworks */,
53ABFDED26799D7700886593 /* ActivityIndicator in Frameworks */,
E192608828D2E5F0002314B4 /* Factory in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1045,6 +1048,7 @@
62666E0327E5017100EC0ECD /* CoreMedia.framework in Frameworks */,
62666E0627E5017A00EC0ECD /* CoreVideo.framework in Frameworks */,
E10EAA4D277BB716000269ED /* Sliders in Frameworks */,
E192608328D2D0DB002314B4 /* Factory in Frameworks */,
62C29E9C26D0FE4200C1D2E7 /* Stinsen in Frameworks */,
62666E0227E5016D00EC0ECD /* CoreGraphics.framework in Frameworks */,
62666E1027E501B400EC0ECD /* VideoToolbox.framework in Frameworks */,
@ -2269,6 +2273,7 @@
E13AF3B928A0C598009093AB /* NukeUI */,
E13AF3BB28A0C59E009093AB /* BlurHashKit */,
E1734D7D28B9578100C66367 /* CollectionView */,
E192608728D2E5F0002314B4 /* Factory */,
);
productName = "JellyfinPlayer tvOS";
productReference = 535870602669D21600D05A09 /* Swiftfin tvOS.app */;
@ -2308,6 +2313,7 @@
E19E6E0628A0B958005C10C8 /* NukeUI */,
E19E6E0928A0BEFF005C10C8 /* BlurHashKit */,
E1734D7B28B9577700C66367 /* CollectionView */,
E192608228D2D0DB002314B4 /* Factory */,
);
productName = JellyfinPlayer;
productReference = 5377CBF1263B596A003A4E83 /* Swiftfin iOS.app */;
@ -2373,6 +2379,7 @@
E19E6E0328A0B958005C10C8 /* XCRemoteSwiftPackageReference "Nuke" */,
E19E6E0828A0BEFF005C10C8 /* XCRemoteSwiftPackageReference "BlurHashKit" */,
E1734D7A28B9577700C66367 /* XCRemoteSwiftPackageReference "CollectionView" */,
E192608128D2D0DB002314B4 /* XCRemoteSwiftPackageReference "Factory" */,
);
productRefGroup = 5377CBF2263B596A003A4E83 /* Products */;
projectDirPath = "";
@ -3447,6 +3454,14 @@
kind = branch;
};
};
E192608128D2D0DB002314B4 /* XCRemoteSwiftPackageReference "Factory" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/hmlongco/Factory";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 1.0.0;
};
};
E19E6E0328A0B958005C10C8 /* XCRemoteSwiftPackageReference "Nuke" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/kean/Nuke";
@ -3609,6 +3624,16 @@
package = E10EAA43277BB646000269ED /* XCRemoteSwiftPackageReference "jellyfin-sdk-swift" */;
productName = JellyfinAPI;
};
E192608228D2D0DB002314B4 /* Factory */ = {
isa = XCSwiftPackageProductDependency;
package = E192608128D2D0DB002314B4 /* XCRemoteSwiftPackageReference "Factory" */;
productName = Factory;
};
E192608728D2E5F0002314B4 /* Factory */ = {
isa = XCSwiftPackageProductDependency;
package = E192608128D2D0DB002314B4 /* XCRemoteSwiftPackageReference "Factory" */;
productName = Factory;
};
E19E6E0428A0B958005C10C8 /* Nuke */ = {
isa = XCSwiftPackageProductDependency;
package = E19E6E0328A0B958005C10C8 /* XCRemoteSwiftPackageReference "Nuke" */;

View File

@ -54,6 +54,15 @@
"version" : "6.3.0"
}
},
{
"identity" : "factory",
"kind" : "remoteSourceControl",
"location" : "https://github.com/hmlongco/Factory",
"state" : {
"revision" : "8557426f3286e20b631ecdac8115242f888656e0",
"version" : "1.2.8"
}
},
{
"identity" : "jellyfin-sdk-swift",
"kind" : "remoteSourceControl",

View File

@ -20,7 +20,6 @@ class AppDelegate: NSObject, UIApplicationDelegate {
// Lazily initialize datastack
_ = SwiftfinStore.dataStack
LogManager.setup()
let audioSession = AVAudioSession.sharedInstance()
do {

View File

@ -6,12 +6,15 @@
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
//
import Factory
import SwiftUI
extension ItemView {
struct PlayButton: View {
@Injected(LogManager.service)
private var logger
@EnvironmentObject
private var itemRouter: ItemCoordinator.Router
@ObservedObject
@ -22,7 +25,7 @@ extension ItemView {
if let selectedVideoPlayerViewModel = viewModel.selectedVideoPlayerViewModel {
itemRouter.route(to: \.videoPlayer, selectedVideoPlayerViewModel)
} else {
LogManager.log.error("Attempted to play item but no playback information available")
logger.error("Attempted to play item but no playback information available")
}
} label: {
ZStack {
@ -47,7 +50,7 @@ extension ItemView {
selectedVideoPlayerViewModel.injectCustomValues(startFromBeginning: true)
itemRouter.route(to: \.videoPlayer, selectedVideoPlayerViewModel)
} else {
LogManager.log.error("Attempted to play item but no playback information available")
logger.error("Attempted to play item but no playback information available")
}
} label: {
Label(L10n.playFromBeginning, systemImage: "gobackward")

View File

@ -10,6 +10,7 @@ import AVFoundation
import AVKit
import Combine
import Defaults
import Factory
import JellyfinAPI
import MediaPlayer
import MobileVLCKit
@ -19,6 +20,10 @@ import UIKit
// TODO: Look at making the VLC player layer a view
class LiveTVPlayerViewController: UIViewController {
@Injected(LogManager.service)
private var logger
// MARK: variables
private var viewModel: VideoPlayerViewModel
@ -532,11 +537,11 @@ extension LiveTVPlayerViewController {
viewModel = newViewModel
if viewModel.streamType == .direct {
LogManager.log.debug("Player set up with direct play stream for item: \(viewModel.item.id ?? .emptyDash)")
logger.debug("Player set up with direct play stream for item: \(viewModel.item.id ?? .emptyDash)")
} else if viewModel.streamType == .transcode && Defaults[.Experimental.forceDirectPlay] {
LogManager.log.debug("Player set up with forced direct stream for item: \(viewModel.item.id ?? .emptyDash)")
logger.debug("Player set up with forced direct stream for item: \(viewModel.item.id ?? .emptyDash)")
} else {
LogManager.log.debug("Player set up with transcoded stream for item: \(viewModel.item.id ?? .emptyDash)")
logger.debug("Player set up with transcoded stream for item: \(viewModel.item.id ?? .emptyDash)")
}
}

View File

@ -10,6 +10,7 @@ import AVFoundation
import AVKit
import Combine
import Defaults
import Factory
import JellyfinAPI
import MediaPlayer
import MobileVLCKit
@ -19,6 +20,10 @@ import UIKit
// TODO: Look at making the VLC player layer a view
class VLCPlayerViewController: UIViewController {
@Injected(LogManager.service)
private var logger
// MARK: variables
private var viewModel: VideoPlayerViewModel
@ -615,11 +620,11 @@ extension VLCPlayerViewController {
viewModel = newViewModel
if viewModel.streamType == .direct {
LogManager.log.debug("Player set up with direct play stream for item: \(viewModel.item.id ?? .emptyDash)")
logger.debug("Player set up with direct play stream for item: \(viewModel.item.id ?? .emptyDash)")
} else if viewModel.streamType == .transcode && Defaults[.Experimental.forceDirectPlay] {
LogManager.log.debug("Player set up with forced direct stream for item: \(viewModel.item.id ?? .emptyDash)")
logger.debug("Player set up with forced direct stream for item: \(viewModel.item.id ?? .emptyDash)")
} else {
LogManager.log.debug("Player set up with transcoded stream for item: \(viewModel.item.id ?? .emptyDash)")
logger.debug("Player set up with transcoded stream for item: \(viewModel.item.id ?? .emptyDash)")
}
}