diff --git a/Demo/Sources/Model/Media.swift b/Demo/Sources/Model/Media.swift index 47f18381f..d3c35fc31 100644 --- a/Demo/Sources/Model/Media.swift +++ b/Demo/Sources/Model/Media.swift @@ -90,7 +90,7 @@ struct Media: Hashable { configuration: .init(position: at(startTime)) ) case let .unbufferedUrl(url): - let configuration = PlayerItemConfiguration( + let configuration = PlaybackConfiguration( position: at(startTime), automaticallyPreservesTimeOffsetFromLive: true, preferredForwardBufferDuration: 1 diff --git a/Sources/CoreBusiness/Extensions/Asset.swift b/Sources/CoreBusiness/Extensions/Asset.swift index cd3cad578..c58260eb7 100644 --- a/Sources/CoreBusiness/Extensions/Asset.swift +++ b/Sources/CoreBusiness/Extensions/Asset.swift @@ -8,7 +8,7 @@ import Foundation import PillarboxPlayer extension Asset { - static func tokenProtected(url: URL, metadata: M, configuration: PlayerItemConfiguration) -> Self { + static func tokenProtected(url: URL, metadata: M, configuration: PlaybackConfiguration) -> Self { let id = UUID() return .custom( url: AkamaiURLCoding.encodeUrl(url, id: id), @@ -18,7 +18,7 @@ extension Asset { ) } - static func encrypted(url: URL, certificateUrl: URL, metadata: M, configuration: PlayerItemConfiguration) -> Self { + static func encrypted(url: URL, certificateUrl: URL, metadata: M, configuration: PlaybackConfiguration) -> Self { .encrypted( url: url, delegate: ContentKeySessionDelegate(certificateUrl: certificateUrl), diff --git a/Sources/CoreBusiness/Extensions/PlayerItem.swift b/Sources/CoreBusiness/Extensions/PlayerItem.swift index 769271251..97b0cda9e 100644 --- a/Sources/CoreBusiness/Extensions/PlayerItem.swift +++ b/Sources/CoreBusiness/Extensions/PlayerItem.swift @@ -25,7 +25,7 @@ public extension PlayerItem { _ urn: String, server: Server = .production, trackerAdapters: [TrackerAdapter] = [], - configuration: PlayerItemConfiguration = .default + configuration: PlaybackConfiguration = .default ) -> Self { .init( publisher: publisher(forUrn: urn, server: server, configuration: configuration), @@ -62,7 +62,7 @@ public extension PlayerItem { url: URL, metadata: M, trackerAdapters: [TrackerAdapter] = [], - configuration: PlayerItemConfiguration = .default + configuration: PlaybackConfiguration = .default ) -> Self where M: AssetMetadata { .init( asset: .tokenProtected(url: url, metadata: metadata, configuration: configuration), @@ -86,7 +86,7 @@ public extension PlayerItem { certificateUrl: URL, metadata: M, trackerAdapters: [TrackerAdapter] = [], - configuration: PlayerItemConfiguration = .default + configuration: PlaybackConfiguration = .default ) -> Self where M: AssetMetadata { .init( asset: .encrypted(url: url, certificateUrl: certificateUrl, metadata: metadata, configuration: configuration), @@ -96,7 +96,7 @@ public extension PlayerItem { } private extension PlayerItem { - static func publisher(forUrn urn: String, server: Server, configuration: PlayerItemConfiguration) -> AnyPublisher, Error> { + static func publisher(forUrn urn: String, server: Server, configuration: PlaybackConfiguration) -> AnyPublisher, Error> { let dataProvider = DataProvider(server: server) return dataProvider.mediaCompositionPublisher(forUrn: urn) .tryMap { response in @@ -106,7 +106,7 @@ private extension PlayerItem { .eraseToAnyPublisher() } - private static func asset(metadata: MediaMetadata, configuration: PlayerItemConfiguration, dataProvider: DataProvider) -> Asset { + private static func asset(metadata: MediaMetadata, configuration: PlaybackConfiguration, dataProvider: DataProvider) -> Asset { if let blockingReason = metadata.blockingReason { return .unavailable(with: DataError.blocked(withMessage: blockingReason.description), metadata: metadata) } @@ -129,8 +129,8 @@ private extension PlayerItem { private static func assetConfiguration( for resource: MediaComposition.Resource, - configuration: PlayerItemConfiguration - ) -> PlayerItemConfiguration { + configuration: PlaybackConfiguration + ) -> PlaybackConfiguration { // Limit buffering and force the player to return to the live edge when re-buffering. This ensures // livestreams cannot be paused and resumed in the past, as requested by business people. guard resource.streamType == .live else { return configuration } diff --git a/Sources/Player/Asset.swift b/Sources/Player/Asset.swift index ff85c7d5f..5ba4572a0 100644 --- a/Sources/Player/Asset.swift +++ b/Sources/Player/Asset.swift @@ -16,16 +16,16 @@ import AVFoundation public struct Asset { let resource: Resource let metadata: M - let configuration: PlayerItemConfiguration + let configuration: PlaybackConfiguration /// Returns a simple asset playable from a URL. /// /// - Parameters: /// - url: The URL to be played. /// - metadata: The metadata associated with the asset. - /// - configuration: The configuration to apply to the player item. + /// - configuration: The configuration to apply to the asset. /// - Returns: The asset. - public static func simple(url: URL, metadata: M, configuration: PlayerItemConfiguration = .default) -> Self { + public static func simple(url: URL, metadata: M, configuration: PlaybackConfiguration = .default) -> Self { .init(resource: .simple(url: url), metadata: metadata, configuration: configuration) } @@ -35,7 +35,7 @@ public struct Asset { /// - url: The URL to be played. /// - delegate: The custom resource loader to use. /// - metadata: The metadata associated with the asset. - /// - configuration: The configuration to apply to the player item. + /// - configuration: The configuration to apply to the asset. /// - Returns: The asset. /// /// The scheme of the URL to be played has to be recognized by the associated resource loader delegate. @@ -43,7 +43,7 @@ public struct Asset { url: URL, delegate: AVAssetResourceLoaderDelegate, metadata: M, - configuration: PlayerItemConfiguration = .default + configuration: PlaybackConfiguration = .default ) -> Self { .init( resource: .custom(url: url, delegate: delegate), @@ -58,13 +58,13 @@ public struct Asset { /// - url: The URL to be played. /// - delegate: The content key session delegate to use. /// - metadata: The metadata associated with the asset. - /// - configuration: The configuration to apply to the player item. + /// - configuration: The configuration to apply to the asset. /// - Returns: The asset. public static func encrypted( url: URL, delegate: AVContentKeySessionDelegate, metadata: M, - configuration: PlayerItemConfiguration = .default + configuration: PlaybackConfiguration = .default ) -> Self { .init( resource: .encrypted(url: url, delegate: delegate), @@ -99,11 +99,11 @@ public extension Asset where M == Void { /// /// - Parameters: /// - url: The URL to be played. - /// - configuration: The configuration to apply to the player item. + /// - configuration: The configuration to apply to the asset. /// - Returns: The asset. static func simple( url: URL, - configuration: PlayerItemConfiguration = .default + configuration: PlaybackConfiguration = .default ) -> Self { .init( resource: .simple(url: url), @@ -117,14 +117,14 @@ public extension Asset where M == Void { /// - Parameters: /// - url: The URL to be played. /// - delegate: The custom resource loader to use. - /// - configuration: The configuration to apply to the player item. + /// - configuration: The configuration to apply to the asset. /// - Returns: The asset. /// /// The scheme of the URL to be played has to be recognized by the associated resource loader delegate. static func custom( url: URL, delegate: AVAssetResourceLoaderDelegate, - configuration: PlayerItemConfiguration = .default + configuration: PlaybackConfiguration = .default ) -> Self { .init( resource: .custom(url: url, delegate: delegate), @@ -138,12 +138,12 @@ public extension Asset where M == Void { /// - Parameters: /// - url: The URL to be played. /// - delegate: The content key session delegate to use. - /// - configuration: The configuration to apply to the player item. + /// - configuration: The configuration to apply to the asset. /// - Returns: The asset. static func encrypted( url: URL, delegate: AVContentKeySessionDelegate, - configuration: PlayerItemConfiguration = .default + configuration: PlaybackConfiguration = .default ) -> Self { .init( resource: .encrypted(url: url, delegate: delegate), diff --git a/Sources/Player/Player.docc/Articles/optimization/optimization-article.md b/Sources/Player/Player.docc/Articles/optimization/optimization-article.md index b92681d24..25370eb13 100644 --- a/Sources/Player/Player.docc/Articles/optimization/optimization-article.md +++ b/Sources/Player/Player.docc/Articles/optimization/optimization-article.md @@ -39,9 +39,9 @@ An empty ``Player`` instance is lightweight, but once loaded with content, it in To minimize resource usage, aim to keep the number of ``Player`` instances loaded with content as low as possible. Consider these strategies: - **Implement a Player Pool:** Instead of creating a new player instance for every need, maintain a pool of reusable players. Borrow a player from the pool when needed and return it when done. -- **Clear Unused Players:** Use ``Player/removeAllItems()`` to empty a player's item queue without destroying the player instance. When reloading previously played content, use ``PlayerItemConfiguration/position`` to resume playback from where it was last interrupted. +- **Clear Unused Players:** Use ``Player/removeAllItems()`` to empty a player's item queue without destroying the player instance. When reloading previously played content, use ``PlaybackConfiguration/position`` to resume playback from where it was last interrupted. - **Leverage Thumbnails:** Display thumbnails representing the first frame or video content to create the illusion of instant playback without loading the actual video. This approach is especially effective in scrollable lists with autoplay functionality. -- **Limit Buffering:** Control the player's buffering behavior by setting ``PlayerItemConfiguration/preferredForwardBufferDuration`` in a ``PlayerItem`` configuration. While the default buffering can be quite aggressive, reducing the buffer duration lowers memory usage but increases the likelihood of playback stalling and re-buffering. Use this setting judiciously to balance resource usage and playback stability. +- **Limit Buffering:** Control the player's buffering behavior by setting ``PlaybackConfiguration/preferredForwardBufferDuration`` in a ``PlayerItem`` configuration. While the default buffering can be quite aggressive, reducing the buffer duration lowers memory usage but increases the likelihood of playback stalling and re-buffering. Use this setting judiciously to balance resource usage and playback stability. ## Implement autoplay wisely diff --git a/Sources/Player/Player.docc/PillarboxPlayer.md b/Sources/Player/Player.docc/PillarboxPlayer.md index 5de72d97c..060469999 100644 --- a/Sources/Player/Player.docc/PillarboxPlayer.md +++ b/Sources/Player/Player.docc/PillarboxPlayer.md @@ -93,14 +93,14 @@ The PillarboxPlayer framework seamlessly integrates with SwiftUI, leveraging its - ``PictureInPicturePersistable`` - ``RoutePickerView`` -### Asset Resource Loading +### Content Loading - - - ``Asset`` +- ``PlaybackConfiguration`` - ``PlayerItem`` -- ``PlayerItemConfiguration`` ### Tracking diff --git a/Sources/Player/PlayerItem.swift b/Sources/Player/PlayerItem.swift index 30de49879..88edf7727 100644 --- a/Sources/Player/PlayerItem.swift +++ b/Sources/Player/PlayerItem.swift @@ -161,7 +161,7 @@ public extension PlayerItem { url: URL, metadata: M, trackerAdapters: [TrackerAdapter] = [], - configuration: PlayerItemConfiguration = .default + configuration: PlaybackConfiguration = .default ) -> Self where M: AssetMetadata { .init( asset: .simple(url: url, metadata: metadata, configuration: configuration), @@ -185,7 +185,7 @@ public extension PlayerItem { delegate: AVAssetResourceLoaderDelegate, metadata: M, trackerAdapters: [TrackerAdapter] = [], - configuration: PlayerItemConfiguration = .default + configuration: PlaybackConfiguration = .default ) -> Self where M: AssetMetadata { .init( asset: .custom(url: url, delegate: delegate, metadata: metadata, configuration: configuration), @@ -207,7 +207,7 @@ public extension PlayerItem { delegate: AVContentKeySessionDelegate, metadata: M, trackerAdapters: [TrackerAdapter] = [], - configuration: PlayerItemConfiguration = .default + configuration: PlaybackConfiguration = .default ) -> Self where M: AssetMetadata { .init( asset: .encrypted(url: url, delegate: delegate, metadata: metadata, configuration: configuration), @@ -227,7 +227,7 @@ public extension PlayerItem { static func simple( url: URL, trackerAdapters: [TrackerAdapter] = [], - configuration: PlayerItemConfiguration = .default + configuration: PlaybackConfiguration = .default ) -> Self { .init( asset: .simple(url: url, configuration: configuration), @@ -249,7 +249,7 @@ public extension PlayerItem { url: URL, delegate: AVAssetResourceLoaderDelegate, trackerAdapters: [TrackerAdapter] = [], - configuration: PlayerItemConfiguration = .default + configuration: PlaybackConfiguration = .default ) -> Self { .init( asset: .custom(url: url, delegate: delegate, configuration: configuration), @@ -269,7 +269,7 @@ public extension PlayerItem { url: URL, delegate: AVContentKeySessionDelegate, trackerAdapters: [TrackerAdapter] = [], - configuration: PlayerItemConfiguration = .default + configuration: PlaybackConfiguration = .default ) -> Self { .init( asset: .encrypted(url: url, delegate: delegate, configuration: configuration), diff --git a/Sources/Player/Types/AssetContent.swift b/Sources/Player/Types/AssetContent.swift index 47e4329bd..40da0a352 100644 --- a/Sources/Player/Types/AssetContent.swift +++ b/Sources/Player/Types/AssetContent.swift @@ -10,7 +10,7 @@ struct AssetContent { let id: UUID let resource: Resource let metadata: PlayerMetadata - let configuration: PlayerItemConfiguration + let configuration: PlaybackConfiguration let dateInterval: DateInterval? static func loading(id: UUID) -> Self { diff --git a/Sources/Player/Types/PlayerItemConfiguration.swift b/Sources/Player/Types/PlaybackConfiguration.swift similarity index 94% rename from Sources/Player/Types/PlayerItemConfiguration.swift rename to Sources/Player/Types/PlaybackConfiguration.swift index 343cdad3c..2df3dbd58 100644 --- a/Sources/Player/Types/PlayerItemConfiguration.swift +++ b/Sources/Player/Types/PlaybackConfiguration.swift @@ -6,8 +6,8 @@ import AVFoundation -/// A player item configuration. -public struct PlayerItemConfiguration { +/// A playback configuration. +public struct PlaybackConfiguration { /// The default configuration. public static let `default` = Self() @@ -29,7 +29,7 @@ public struct PlayerItemConfiguration { /// disruption. public let preferredForwardBufferDuration: TimeInterval - /// Creates a player item configuration. + /// Creates a playback configuration. public init( position: Position = at(.zero), automaticallyPreservesTimeOffsetFromLive: Bool = false, diff --git a/Tests/PlayerTests/Player/BlockedTimeRangeTests.swift b/Tests/PlayerTests/Player/BlockedTimeRangeTests.swift index 460410b72..3fe3b69c7 100644 --- a/Tests/PlayerTests/Player/BlockedTimeRangeTests.swift +++ b/Tests/PlayerTests/Player/BlockedTimeRangeTests.swift @@ -66,14 +66,14 @@ final class BlockedTimeRangeTests: TestCase { } func testBlockedTimeRangeTraversal() { - let configuration = PlayerItemConfiguration(position: at(.init(value: 29, timescale: 1))) + let configuration = PlaybackConfiguration(position: at(.init(value: 29, timescale: 1))) let player = Player(item: .simple(url: Stream.onDemand.url, metadata: MetadataWithBlockedTimeRange(), configuration: configuration)) player.play() expect(player.time()).toEventually(beGreaterThan(kBlockedTimeRange.end)) } func testOnDemandStartInBlockedTimeRange() { - let configuration = PlayerItemConfiguration(position: at(.init(value: 30, timescale: 1))) + let configuration = PlaybackConfiguration(position: at(.init(value: 30, timescale: 1))) let player = Player(item: .simple(url: Stream.onDemand.url, metadata: MetadataWithBlockedTimeRange(), configuration: configuration)) expect(player.time()).toEventually(equal(kBlockedTimeRange.end)) } diff --git a/Tests/PlayerTests/Player/SeekTests.swift b/Tests/PlayerTests/Player/SeekTests.swift index 1e2d04e57..ff4329174 100644 --- a/Tests/PlayerTests/Player/SeekTests.swift +++ b/Tests/PlayerTests/Player/SeekTests.swift @@ -107,13 +107,13 @@ final class SeekTests: TestCase { } func testOnDemandStartAtTime() { - let configuration = PlayerItemConfiguration(position: at(.init(value: 10, timescale: 1))) + let configuration = PlaybackConfiguration(position: at(.init(value: 10, timescale: 1))) let player = Player(item: .simple(url: Stream.onDemand.url, configuration: configuration)) expect(player.time().seconds).toEventually(equal(10)) } func testDvrStartAtTime() { - let configuration = PlayerItemConfiguration(position: at(.init(value: 10, timescale: 1))) + let configuration = PlaybackConfiguration(position: at(.init(value: 10, timescale: 1))) let player = Player(item: .simple(url: Stream.dvr.url, configuration: configuration)) expect(player.time().seconds).toEventually(equal(10)) }