Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: program date time instant clipping #63

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -77,6 +77,18 @@ class SinglePlayerExampleController: UIViewController {
}
}

var programStartTimeInSeconds: Double = .nan {
didSet {
preparePlayerViewController()
}
}

var programEndTimeInSeconds: Double = .nan {
didSet {
preparePlayerViewController()
}
}

func preparePlayerViewController() {
playerViewController.prepare(
playbackID: playbackID,
@@ -344,6 +356,44 @@ class SinglePlayerExampleController: UIViewController {
assetEndTimeTextField.keyboardType = .decimalPad
assetEndTimeTextField.placeholder = "Clip ending time if desired"

let programStartTimeTextField = UITextField(
frame: .zero,
primaryAction: UIAction(
handler: { action in
if let text = (action.sender as? UITextField)?.text {
self.programStartTimeInSeconds = (
try? Double(
text,
format: .number
)
) ?? .nan
}
}
)
)

programStartTimeTextField.keyboardType = .decimalPad
programStartTimeTextField.placeholder = "Clip ending program date and time if desired"

let programEndTimeTextField = UITextField(
frame: .zero,
primaryAction: UIAction(
handler: { action in
if let text = (action.sender as? UITextField)?.text {
self.programEndTimeInSeconds = (
try? Double(
text,
format: .number
)
) ?? .nan
}
}
)
)

programEndTimeTextField.keyboardType = .decimalPad
programEndTimeTextField.placeholder = "Clip ending program date and time if desired"

let stackView = UIStackView(
arrangedSubviews: [
maximumResolutionLabel,
Original file line number Diff line number Diff line change
@@ -77,6 +77,24 @@ internal extension URLComponents {
)
)
}

if !publicPlaybackOptions.instantClipping.programStartTimeEpochInSeconds.isNaN {
queryItems.append(
URLQueryItem(
name: "program_start_time",
value: publicPlaybackOptions.instantClipping.programStartTimeEpochInSeconds.description
)
)
}

if !publicPlaybackOptions.instantClipping.programEndTimeEpochInSeconds.isNaN {
queryItems.append(
URLQueryItem(
name: "program_end_time",
value: publicPlaybackOptions.instantClipping.programEndTimeEpochInSeconds.description
)
)
}
}

self.queryItems = queryItems
66 changes: 65 additions & 1 deletion Sources/MuxPlayerSwift/PublicAPI/Options/PlaybackOptions.swift
Original file line number Diff line number Diff line change
@@ -82,8 +82,14 @@ public struct InstantClipping: Equatable {
var assetStartTimeInSeconds: Double
var assetEndTimeInSeconds: Double

var programStartTimeEpochInSeconds: Double
var programEndTimeEpochInSeconds: Double

var noInstantClipping: Bool {
return self.assetStartTimeInSeconds.isNaN && self.assetEndTimeInSeconds.isNaN
return self.assetStartTimeInSeconds.isNaN &&
self.assetEndTimeInSeconds.isNaN &&
self.programStartTimeEpochInSeconds.isNaN &&
self.programEndTimeEpochInSeconds.isNaN
}

/// Omits instant clipping when loading a playback ID.
@@ -92,6 +98,8 @@ public struct InstantClipping: Equatable {
init() {
self.assetStartTimeInSeconds = .nan
self.assetEndTimeInSeconds = .nan
self.programStartTimeEpochInSeconds = .nan
self.programEndTimeEpochInSeconds = .nan
}

/// Streams a clip whose starting time are based on
@@ -125,6 +133,8 @@ public struct InstantClipping: Equatable {
) {
self.assetStartTimeInSeconds = assetStartTimeInSeconds
self.assetEndTimeInSeconds = assetEndTimeInSeconds
self.programStartTimeEpochInSeconds = .nan
self.programEndTimeEpochInSeconds = .nan
}

/// Streams a clip whose starting time is based on
@@ -140,6 +150,8 @@ public struct InstantClipping: Equatable {
) {
self.assetStartTimeInSeconds = assetStartTimeInSeconds
self.assetEndTimeInSeconds = .nan
self.programStartTimeEpochInSeconds = .nan
self.programEndTimeEpochInSeconds = .nan
}

/// Streams a clip with an ending time based on the
@@ -154,6 +166,58 @@ public struct InstantClipping: Equatable {
) {
self.assetStartTimeInSeconds = .nan
self.assetEndTimeInSeconds = assetEndTimeInSeconds
self.programStartTimeEpochInSeconds = .nan
self.programEndTimeEpochInSeconds = .nan
}


/// Streams a clip with a start and end time based
/// on the program date time exposed in the video stream
/// metadata. Only supported for use with livestream-
/// originating assets.
/// - Parameters:
/// - programStartTimeEpochInSeconds: program date and
/// time epoch timestamp with which to start the clip
/// - programEndTimeEpochInSeconds: program date and
/// time epoch timestamp with which to end the clip
public init(
programStartTimeEpochInSeconds: Double,
programEndTimeEpochInSeconds: Double
) {
self.assetStartTimeInSeconds = .nan
self.assetEndTimeInSeconds = .nan
self.programStartTimeEpochInSeconds = programStartTimeEpochInSeconds
self.programEndTimeEpochInSeconds = programEndTimeEpochInSeconds
}

/// Streams a clip with a start time based on the program
/// date time exposed in the video stream metadata. Only
/// supported for use with livestream-originating assets.
/// - Parameters:
/// - programStartTimeEpochInSeconds: program date and
/// time epoch timestamp with which to start the clip
public init(
programStartTimeEpochInSeconds: Double
) {
self.assetStartTimeInSeconds = .nan
self.assetEndTimeInSeconds = .nan
self.programStartTimeEpochInSeconds = programStartTimeEpochInSeconds
self.programEndTimeEpochInSeconds = .nan
}

/// Streams a clip with a start time based on the program
/// date time exposed in the video stream metadata. Only
/// supported for use with livestream-originating assets.
/// - Parameters:
/// - programEndTimeEpochInSeconds: program date and
/// time epoch timestamp with which to start the clip
public init(
programEndTimeEpochInSeconds: Double
) {
self.assetStartTimeInSeconds = .nan
self.assetEndTimeInSeconds = .nan
self.programStartTimeEpochInSeconds = .nan
self.programEndTimeEpochInSeconds = programEndTimeEpochInSeconds
}
}

94 changes: 94 additions & 0 deletions Tests/MuxPlayerSwift/PlaybackURLTests.swift
Original file line number Diff line number Diff line change
@@ -595,4 +595,98 @@ final class PlaybackURLTests: XCTestCase {
})
)
}

func testClippingProgramStartTime() throws {
let item = AVPlayerItem(
playbackID: "abc123",
playbackOptions: PlaybackOptions(
clipping: InstantClipping(
programStartTimeEpochInSeconds: 1730214200
)
)
)

let url = try XCTUnwrap(
(item.asset as? AVURLAsset)?.url,
"Expected player item with URL"
)

let queryItems = try XCTUnwrap(
URLComponents(
url: url,
resolvingAgainstBaseURL: false
)?.queryItems
)

XCTAssertNotNil(
queryItems.first(where: {
$0.name == "program_start_time"
})
)
}

func testClippingProgramEndTime() throws {
let item = AVPlayerItem(
playbackID: "abc123",
playbackOptions: PlaybackOptions(
clipping: InstantClipping(
programEndTimeEpochInSeconds: 1730217200
)
)
)

let url = try XCTUnwrap(
(item.asset as? AVURLAsset)?.url,
"Expected player item with URL"
)

let queryItems = try XCTUnwrap(
URLComponents(
url: url,
resolvingAgainstBaseURL: false
)?.queryItems
)

XCTAssertNotNil(
queryItems.first(where: {
$0.name == "program_end_time"
})
)
}

func testClippingProgramStartAndEndTime() throws {
let item = AVPlayerItem(
playbackID: "abc123",
playbackOptions: PlaybackOptions(
clipping: InstantClipping(
programStartTimeEpochInSeconds: 1730214200,
programEndTimeEpochInSeconds: 1730217200
)
)
)

let url = try XCTUnwrap(
(item.asset as? AVURLAsset)?.url,
"Expected player item with URL"
)

let queryItems = try XCTUnwrap(
URLComponents(
url: url,
resolvingAgainstBaseURL: false
)?.queryItems
)

XCTAssertNotNil(
queryItems.first(where: {
$0.name == "program_start_time"
})
)

XCTAssertNotNil(
queryItems.first(where: {
$0.name == "program_end_time"
})
)
}
}