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

Prevent SRG SSR livestreams from being played in the past #99

Merged
merged 9 commits into from
Oct 11, 2022
Merged
11 changes: 8 additions & 3 deletions Demo/Sources/BasicPlayerView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,15 @@ struct BasicPlayerView: View {
@StateObject private var player = Player(item: AVPlayerItem(url: Stream.appleAdvanced_16_9_HEVC_h264))

var body: some View {
VideoView(player: player)
.onAppear {
player.play()
ZStack {
VideoView(player: player)
if player.isBuffering {
ProgressView()
}
}
.onAppear {
player.play()
}
}
}

Expand Down
7 changes: 7 additions & 0 deletions Demo/Sources/Media.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@
import Foundation

struct Media: Identifiable {
static var liveMedia = Media(
id: "live",
title: "Couleur 3",
description: "Couleur 3 livestream",
source: .url(Stream.couleur3_livestream)
)

static var urnPlaylist: [Media] = [
Media(
id: "playlist:1",
Expand Down
6 changes: 3 additions & 3 deletions Demo/Sources/MediasView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,13 @@ struct MediasView: View {
id: "assets:video_live_hls",
title: "Video livestream - HLS",
description: "Couleur 3 en vidéo",
source: .url(URL(string: "https://rtsc3video.akamaized.net/hls/live/2042837/c3video/3/playlist.m3u8?dw=0")!)
source: .url(Stream.couleur3_livestream)
),
Media(
id: "assets:video_live_dvr_hls",
title: "Video livestream with DVR - HLS",
description: "Couleur 3 en vidéo",
source: .url(URL(string: "https://rtsc3video.akamaized.net/hls/live/2042837/c3video/3/playlist.m3u8")!)
source: .url(Stream.couleur3_livestream_dvr)
),
Media(
id: "assets:video_live_dvr_timestamps_hls",
Expand Down Expand Up @@ -172,7 +172,7 @@ struct MediasView: View {

// MARK: Preview

struct DemosView_Previews: PreviewProvider {
struct MediasView_Previews: PreviewProvider {
static var previews: some View {
NavigationStack {
MediasView()
Expand Down
12 changes: 9 additions & 3 deletions Demo/Sources/PlayerView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ private struct PreviousButton: View {

struct PlayerView: View {
let medias: [Media]
let buffered: Bool

@StateObject private var player = Player()
@State private var isUserInterfaceHidden = false
Expand Down Expand Up @@ -125,16 +126,21 @@ struct PlayerView: View {
}
}

init(medias: [Media]) {
init(medias: [Media], buffered: Bool = true) {
self.medias = medias
self.buffered = buffered
}

init(media: Media) {
self.init(medias: [media])
init(media: Media, buffered: Bool = true) {
self.init(medias: [media], buffered: buffered)
}

private func play() {
medias.compactMap(\.source.playerItem).forEach { item in
if !buffered {
item.automaticallyPreservesTimeOffsetFromLive = true
item.preferredForwardBufferDuration = 1
}
player.append(item)
}
player.play()
Expand Down
3 changes: 3 additions & 0 deletions Demo/Sources/ShowcaseView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ struct ShowcaseView: View {
Cell(title: "Basic") {
BasicPlayerView()
}
Cell(title: "Unbuffered live playback") {
PlayerView(media: .liveMedia, buffered: false)
}
Cell(title: "Stories") {
StoriesView()
}
Expand Down
5 changes: 5 additions & 0 deletions Demo/Sources/Stream.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,10 @@ enum Stream {
static let appleAdvanced_16_9_fMP4 = URL(string: "https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8")!
static let appleAdvanced_16_9_HEVC_h264 = URL(string: "https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_adv_example_hevc/master.m3u8")!

// Livestreams
static let couleur3_livestream = URL(string: "https://rtsc3video.akamaized.net/hls/live/2042837/c3video/3/playlist.m3u8?dw=0")!
static let couleur3_livestream_dvr = URL(string: "https://rtsc3video.akamaized.net/hls/live/2042837/c3video/3/playlist.m3u8")!

// Other
static let local = URL(string: "http://localhost:8123/on_demand/master.m3u8")!
}
31 changes: 27 additions & 4 deletions Sources/CoreBusiness/PlayerItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,35 @@ public extension AVPlayerItem {
/// Create a player item from a URN played in the specified environment.
/// - Parameters:
/// - urn: The URN to play.
/// - automaticallyLoadedAssetKeys: The asset keys to load before the item is ready to play, ensuring they are
/// available, but increasing startup time.
/// - automaticallyLoadedAssetKeys: The asset keys to load before the item is ready to play.
/// - environment: The environment which the URN is played from.
convenience init(urn: String, automaticallyLoadedAssetKeys: [String], environment: Environment = .production) {
convenience init(urn: String, automaticallyLoadedAssetKeys keys: [String], environment: Environment = .production) {
self.init(asset: Self.asset(fromUrn: urn, environment: environment), automaticallyLoadedAssetKeys: keys)
preventLivestreamDelayedPlayback()
}

/// Create a player item from a URN played in the specified environment. Loads standard asset keys before the item
/// is ready to play.
/// - Parameters:
/// - urn: The URN to play.
/// - environment: The environment which the URN is played from.
convenience init(urn: String, environment: Environment = .production) {
self.init(asset: Self.asset(fromUrn: urn, environment: environment))
preventLivestreamDelayedPlayback()
}

private static func asset(fromUrn urn: String, environment: Environment) -> AVAsset {
let asset = AVURLAsset(url: URLCoding.encodeUrl(fromUrn: urn))
asset.resourceLoader.setDelegate(kAssetResourceLoaders[environment], queue: .global(qos: .userInitiated))
self.init(asset: asset, automaticallyLoadedAssetKeys: automaticallyLoadedAssetKeys)
return asset
}

/// 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.
///
/// Remark: These settings do not negatively affect on-demand or DVR livestream playback.
private func preventLivestreamDelayedPlayback() {
automaticallyPreservesTimeOffsetFromLive = true
preferredForwardBufferDuration = 1
}
}
7 changes: 3 additions & 4 deletions Sources/Player/PlayerItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@ public extension AVPlayerItem {
/// Create a player item from a URL.
/// - Parameters:
/// - url: The URL to play.
/// - automaticallyLoadedAssetKeys: The asset keys to load before the item is ready to play, ensuring they are
/// available, but increasing startup time.
convenience init(url: URL, automaticallyLoadedAssetKeys: [String]) {
/// - automaticallyLoadedAssetKeys: The asset keys to load before the item is ready to play.
convenience init(url: URL, automaticallyLoadedAssetKeys keys: [String]) {
let asset = AVURLAsset(url: url)
self.init(asset: asset, automaticallyLoadedAssetKeys: automaticallyLoadedAssetKeys)
self.init(asset: asset, automaticallyLoadedAssetKeys: keys)
}
}