Skip to content

Commit

Permalink
Add audio tracks and subtitles analytics (#552)
Browse files Browse the repository at this point in the history
waliid authored Sep 4, 2023
1 parent 4b2f01e commit b35bbf4
Showing 3 changed files with 128 additions and 11 deletions.
22 changes: 19 additions & 3 deletions Sources/Analytics/CommandersAct/CommandersActLabels.swift
Original file line number Diff line number Diff line change
@@ -13,6 +13,7 @@ public struct CommandersActLabels: Decodable {
private let _media_position: String?
private let _media_timeshift: String?
private let _media_volume: String?
private let _media_subtitles_on: String?

let event_name: String?
let listener_session_id: String?
@@ -96,6 +97,18 @@ public struct CommandersActLabels: Decodable {
extract(\._media_volume)
}

/// The value of `media_subtitles_on`.
public var media_subtitles_on: Bool? {
// swiftlint:disable:previous discouraged_optional_boolean
extract(\._media_subtitles_on)
}

/// The value of `media_subtitle_selection`.
public var media_subtitle_selection: String?

/// The value of `media_audio_track`.
public var media_audio_track: String?

private func extract<T>(_ keyPath: KeyPath<Self, String?>) -> T? where T: LosslessStringConvertible {
guard let value = self[keyPath: keyPath] else { return nil }
return .init(value)
@@ -104,6 +117,10 @@ public struct CommandersActLabels: Decodable {

private extension CommandersActLabels {
enum CodingKeys: String, CodingKey {
case _media_position = "media_position"
case _media_timeshift = "media_timeshift"
case _media_volume = "media_volume"
case _media_subtitles_on = "media_subtitles_on"
case event_name
case listener_session_id
case media_title
@@ -126,8 +143,7 @@ private extension CommandersActLabels {
case content_title
case media_player_display
case media_player_version
case _media_position = "media_position"
case _media_timeshift = "media_timeshift"
case _media_volume = "media_volume"
case media_subtitle_selection
case media_audio_track
}
}
41 changes: 33 additions & 8 deletions Sources/Analytics/CommandersAct/CommandersActTracker.swift
Original file line number Diff line number Diff line change
@@ -95,17 +95,42 @@ private extension CommandersActTracker {
return Int(AVAudioSession.sharedInstance().outputVolume * 100)
}

private func bitrate(for player: Player) -> Int {
guard let event = player.systemPlayer.currentItem?.accessLog()?.events.last else { return 0 }
return Int(max(event.indicatedBitrate, 0))
private func languageCode(from option: AVMediaSelectionOption?) -> String {
option?.locale?.language.languageCode?.identifier.uppercased() ?? "UND"
}

private func audioTrack(for player: Player) -> String {
switch player.currentMediaOption(for: .audible) {
case let .on(option):
return languageCode(from: option)
default:
return languageCode(from: nil)
}
}

private func subtitleLabels(for player: Player) -> [String: String] {
switch player.currentMediaOption(for: .legible) {
case let .on(option) where !option.hasMediaCharacteristic(.containsOnlyForcedSubtitles):
return [
"media_subtitles_on": "true",
"media_subtitle_selection": "\(languageCode(from: option))"
]
default:
return [
"media_subtitles_on": "false"
]
}
}

func labels(for player: Player) -> [String: String] {
metadata.labels.merging([
"media_player_display": "Pillarbox",
"media_player_version": Player.version,
"media_volume": "\(volume(for: player))"
]) { _, new in new }
metadata.labels
.merging([
"media_player_display": "Pillarbox",
"media_player_version": Player.version,
"media_volume": "\(volume(for: player))",
"media_audio_track": "\(audioTrack(for: player))"
]) { _, new in new }
.merging(subtitleLabels(for: player)) { _, new in new }
}
}

Original file line number Diff line number Diff line change
@@ -21,6 +21,7 @@ final class CommandersActTrackerMetadataTests: CommandersActTestCase {
expect(labels.media_player_version).notTo(beEmpty())
expect(labels.media_volume).notTo(beNil())
expect(labels.media_title).to(equal("name"))
expect(labels.media_audio_track).to(equal("UND"))
}
) {
player = Player(item: .simple(
@@ -57,6 +58,7 @@ final class CommandersActTrackerMetadataTests: CommandersActTestCase {
expect(labels.media_player_version).notTo(beEmpty())
expect(labels.media_volume).notTo(beNil())
expect(labels.media_title).to(equal("name"))
expect(labels.media_audio_track).to(equal("UND"))
}
) {
player = nil
@@ -83,4 +85,78 @@ final class CommandersActTrackerMetadataTests: CommandersActTestCase {
player?.play()
}
}

func testAudioTrack() {
let player = Player(item: .simple(
url: Stream.onDemandWithOptions.url,
metadata: AssetMetadataMock(),
trackerAdapters: [
CommandersActTracker.adapter { _ in
.test(streamType: .onDemand)
}
]
))

player.setMediaSelection(preferredLanguages: ["fr"], for: .audible)
player.play()
expect(player.playbackState).toEventually(equal(.playing))

expectAtLeastHits(
.pause { labels in
expect(labels.media_audio_track).to(equal("FR"))
}
) {
player.pause()
}
}

func testSubtitlesOff() {
let player = Player(item: .simple(
url: Stream.onDemandWithOptions.url,
metadata: AssetMetadataMock(),
trackerAdapters: [
CommandersActTracker.adapter { _ in
.test(streamType: .onDemand)
}
]
))

player.play()
expect(player.playbackState).toEventually(equal(.playing))
player.select(mediaOption: .off, for: .legible)
expect(player.currentMediaOption(for: .legible)).toEventually(equal(.off))

expectAtLeastHits(
.pause { labels in
expect(labels.media_subtitles_on).to(beFalse())
}
) {
player.pause()
}
}

func testSubtitlesOn() {
let player = Player(item: .simple(
url: Stream.onDemandWithOptions.url,
metadata: AssetMetadataMock(),
trackerAdapters: [
CommandersActTracker.adapter { _ in
.test(streamType: .onDemand)
}
]
))

player.setMediaSelection(preferredLanguages: ["fr"], for: .legible)
player.play()
expect(player.playbackState).toEventually(equal(.playing))

expectAtLeastHits(
.pause { labels in
expect(labels.media_subtitles_on).to(beTrue())
expect(labels.media_subtitle_selection).to(equal("FR"))
}
) {
player.pause()
}
}
}

0 comments on commit b35bbf4

Please sign in to comment.