Skip to content

Commit

Permalink
Fix player observability in response to media accessibility updates (#…
Browse files Browse the repository at this point in the history
…1061)

Co-authored-by: Walid Kayhal <[email protected]>
  • Loading branch information
defagos and waliid authored Nov 10, 2024
1 parent 0472f0d commit 74850c4
Show file tree
Hide file tree
Showing 15 changed files with 116 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/Quick/Nimble.git",
"state" : {
"revision" : "6416749c3c0488664fff6b42f8bf3ea8dc282ca1",
"version" : "13.6.0"
"revision" : "f3bedd7c2a858f4513e7d004def200a69238db2f",
"version" : "13.6.2"
}
},
{
Expand Down
2 changes: 1 addition & 1 deletion Demo/Sources/Model/MediaDescription.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ enum MediaDescription {
}

static func style(for media: SRGMedia) -> Style {
media.timeAvailability(at: Date()) == .available ? .standard : .disabled
media.timeAvailability(at: .now) == .available ? .standard : .disabled
}

static func date(for media: SRGMedia) -> String {
Expand Down
4 changes: 2 additions & 2 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/Quick/Nimble.git",
"state" : {
"revision" : "6416749c3c0488664fff6b42f8bf3ea8dc282ca1",
"version" : "13.6.0"
"revision" : "f3bedd7c2a858f4513e7d004def200a69238db2f",
"version" : "13.6.2"
}
},
{
Expand Down
32 changes: 32 additions & 0 deletions Sources/Circumspect/Expectations/ExpectValue.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//
// Copyright (c) SRG SSR. All rights reserved.
//
// License information is available from the LICENSE file.
//

import Combine
import XCTest

public extension XCTestCase {
/// Expects a publisher to emit at least a value.
func expectValue<P>(
from publisher: P,
timeout: DispatchTimeInterval = .seconds(20),
file: StaticString = #file,
line: UInt = #line,
while executing: (() -> Void)? = nil
) where P: Publisher {
expectSuccess(from: publisher.first(), timeout: timeout, file: file, line: line, while: executing)
}

/// Expects an observable object to publish at least a change.
func expectChange<O>(
from object: O,
timeout: DispatchTimeInterval = .seconds(20),
file: StaticString = #file,
line: UInt = #line,
while executing: (() -> Void)? = nil
) where O: ObservableObject {
expectValue(from: object.objectWillChange, timeout: timeout, file: file, line: line, while: executing)
}
}
2 changes: 1 addition & 1 deletion Sources/Core/Publisher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ public extension Publisher {
) -> AnyPublisher<DateInterval, Failure> where S: Scheduler, S.SchedulerTimeType == DispatchQueue.SchedulerTimeType {
measureInterval(using: scheduler)
.map { stride in
let date = Date()
let date = Date.now
return DateInterval(start: date.advanced(by: -TimeInterval(from: stride)), end: date)
}
.eraseToAnyPublisher()
Expand Down
6 changes: 3 additions & 3 deletions Sources/Core/Stopwatch.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,20 @@ public final class Stopwatch {
/// Starts the stopwatch.
public func start() {
guard date == nil else { return }
date = Date()
date = .now
}

/// Stops the stopwatch.
public func stop() {
guard let date else { return }
total += Date().timeIntervalSince(date)
total += Date.now.timeIntervalSince(date)
self.date = nil
}

/// The time accumulated by the stopwatch.
public func time() -> TimeInterval {
if let date {
return total + Date().timeIntervalSince(date)
return total + Date.now.timeIntervalSince(date)
}
else {
return total
Expand Down
4 changes: 2 additions & 2 deletions Sources/Monitoring/MetricsTracker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,10 @@ public final class MetricsTracker: PlayerItemTracker {
sendEvent(name: .start, data: startData(from: events))
startHeartbeat()
case .stall:
stallDate = Date()
stallDate = .now
case .resumeAfterStall:
guard let stallDate else { break }
stallDuration += Date().timeIntervalSince(stallDate)
stallDuration += Date.now.timeIntervalSince(stallDate)
case let .failure(error):
if !session.isStarted {
session.start()
Expand Down
2 changes: 1 addition & 1 deletion Sources/Monitoring/Types/MetricPayload.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ struct MetricPayload<Data>: Encodable where Data: Encodable {
let version = 1
let sessionId: String
let eventName: EventName
let timestamp = Date().timestamp
let timestamp = Date.now.timestamp
let data: Data
}
6 changes: 4 additions & 2 deletions Sources/Player/MediaSelection/MediaSelectionProperties.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@
import AVFoundation

struct MediaSelectionProperties: Equatable {
static let empty = Self(groups: [:], selection: nil)
static let empty = Self(groups: [:], selection: nil, settingsChangeDate: .now)

private let groups: [AVMediaCharacteristic: AVMediaSelectionGroup]
let selection: AVMediaSelection?
private let settingsChangeDate: Date

init(groups: [AVMediaCharacteristic: AVMediaSelectionGroup], selection: AVMediaSelection?) {
init(groups: [AVMediaCharacteristic: AVMediaSelectionGroup], selection: AVMediaSelection?, settingsChangeDate: Date) {
self.groups = groups
self.selection = selection
self.settingsChangeDate = settingsChangeDate
}
}

Expand Down
4 changes: 2 additions & 2 deletions Sources/Player/PlayerItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public final class PlayerItem: Equatable {
Publishers.PublishAndRepeat(onOutputFrom: Self.trigger.signal(activatedBy: TriggerId.reset(id))) { [id] in
Publishers.CombineLatest(
publisher,
Just(Date()).setFailureType(to: P.Failure.self)
Just(Date.now).setFailureType(to: P.Failure.self)
)
.handleEvents(receiveOutput: { asset, _ in
trackerAdapters.forEach { adapter in
Expand All @@ -49,7 +49,7 @@ public final class PlayerItem: Equatable {
Publishers.CombineLatest3(
Just(asset),
metadataMapper(asset.metadata).playerMetadataPublisher(),
Just(DateInterval(start: startDate, end: Date()))
Just(DateInterval(start: startDate, end: .now))
)
}
.switchToLatest()
Expand Down
15 changes: 10 additions & 5 deletions Sources/Player/Publishers/AVPlayerItemPublishers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,10 @@ extension AVPlayerItem {
Publishers.CombineLatest3(
asset.mediaSelectionGroupsPublisher(),
mediaSelectionPublisher(),
NotificationCenter.default.publisher(for: kMACaptionAppearanceSettingsChangedNotification as Notification.Name)
.map { _ in }
.prepend(())
mediaAccessibilityCaptionAppearanceSettingsChangeDatePublisher()
)
.map { groups, selection, _ in
MediaSelectionProperties(groups: groups, selection: selection)
.map { groups, selection, settingsChangeDate in
MediaSelectionProperties(groups: groups, selection: selection, settingsChangeDate: settingsChangeDate)
}
.prepend(.empty)
.removeDuplicates()
Expand All @@ -102,6 +100,13 @@ extension AVPlayerItem {
.eraseToAnyPublisher()
}

private func mediaAccessibilityCaptionAppearanceSettingsChangeDatePublisher() -> AnyPublisher<Date, Never> {
NotificationCenter.default.publisher(for: kMACaptionAppearanceSettingsChangedNotification as Notification.Name)
.map { _ in .now }
.prepend(.now)
.eraseToAnyPublisher()
}

private func minimumTimeOffsetFromLivePublisher() -> AnyPublisher<CMTime, Never> {
asset.propertyPublisher(.minimumTimeOffsetFromLive)
.replaceError(with: .invalid)
Expand Down
2 changes: 1 addition & 1 deletion Sources/Player/Publishers/PlayerItemPublishers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ extension PlayerItem {
$content
.compactMap(\.dateInterval)
.removeDuplicates(),
Just(Date())
Just(Date.now)
)
.map { dateInterval, startDate in
MetricEvent(
Expand Down
40 changes: 40 additions & 0 deletions Tests/CircumspectTests/Expectations/ExpectValueTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//
// Copyright (c) SRG SSR. All rights reserved.
//
// License information is available from the LICENSE file.
//

@testable import PillarboxCircumspect

import Combine
import XCTest

private class Object: ObservableObject {
@Published var value = 0
}

final class ExpectValueTests: XCTestCase {
func testSingleValue() {
expectValue(from: Just(1))
}

func testMultipleValues() {
expectValue(from: [1, 2, 3].publisher)
}

func testSingleChange() {
let object = Object()
expectChange(from: object) {
object.value = 1
}
}

func testMultipleChanges() {
let object = Object()
expectChange(from: object) {
object.value = 1
object.value = 2
object.value = 3
}
}
}
15 changes: 15 additions & 0 deletions Tests/PlayerTests/MediaSelection/MediaSelectionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import AVFoundation
import Nimble
import PillarboxCircumspect
import PillarboxStreams

final class MediaSelectionTests: TestCase {
Expand Down Expand Up @@ -272,6 +273,20 @@ final class MediaSelectionTests: TestCase {
expect(player.selectedMediaOption(for: .legible)).toEventually(equal(.automatic))
expect(player.currentMediaOption(for: .legible)).to(equal(.off))
}

func testObservabilityWhenTogglingBetweenOffAndAutomatic() {
MediaAccessibilityDisplayType.forcedOnly.apply()

let player = Player(item: .simple(url: Stream.onDemandWithOptions.url))
expect(player.mediaSelectionOptions(for: .legible)).toEventuallyNot(beEmpty())

expectChange(from: player) {
player.select(mediaOption: .automatic, for: .legible)
}
expectChange(from: player) {
player.select(mediaOption: .off, for: .legible)
}
}
}

private extension Player {
Expand Down
10 changes: 0 additions & 10 deletions Tests/PlayerTests/Player/QueueTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -115,16 +115,6 @@ final class QueueTests: TestCase {
expect(player.currentItem).to(equal(item2))
}

func testFailingUnauthorizedItemBetweenPlayableItems() {
let item1 = PlayerItem.simple(url: Stream.shortOnDemand.url)
let item2 = PlayerItem.simple(url: Stream.unauthorized.url)
let item3 = PlayerItem.simple(url: Stream.onDemand.url)
let player = Player(items: [item1, item2, item3])
player.play()
expect(player.urls).toEventually(beEmpty())
expect(player.currentItem).to(equal(item2))
}

func testFailingMp3ItemBetweenPlayableItems() {
let item1 = PlayerItem.simple(url: Stream.shortOnDemand.url)
let item2 = PlayerItem.simple(url: Stream.unavailableMp3.url)
Expand Down

0 comments on commit 74850c4

Please sign in to comment.