Created
May 7, 2025 08:42
-
-
Save Eggers-CGI/447c2931eea694de5ba12fc6e9efd2c4 to your computer and use it in GitHub Desktop.
BUG: Apple tvOS - flickering chapters when using artwork image in LIVE-Streams
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import SwiftUI | |
import AVKit | |
struct Chapter: Identifiable { | |
let id = UUID() | |
let title: String | |
let seconds: Double | |
var artworkData: Data? | |
} | |
class ChaptersLoader: ObservableObject { | |
@Published var chapters: [Chapter] = [] | |
@Published var ready = false | |
let baseChapters = [ | |
("Intro", 0.0), | |
("Chapter 1", 60.0), | |
("Chapter 2", 120.0), | |
("Chapter 3", 180.0) | |
] | |
func load() { | |
var loaded: [Chapter] = [] | |
let group = DispatchGroup() | |
for (title, seconds) in baseChapters { | |
group.enter() | |
let url = URL(string: "https://cataas.com/cat")! | |
URLSession.shared.dataTask(with: url) { data, _, _ in | |
var chapter = Chapter(title: title, seconds: seconds, artworkData: nil) | |
if let data = data { | |
chapter.artworkData = data | |
} | |
loaded.append(chapter) | |
group.leave() | |
}.resume() | |
} | |
group.notify(queue: .main) { | |
loaded.sort { $0.seconds < $1.seconds } | |
self.chapters = loaded | |
self.ready = true | |
} | |
} | |
} | |
struct AVPlayerViewControllerRepresentable: UIViewControllerRepresentable { | |
let streamURL: URL | |
let chapters: [Chapter] | |
func makeUIViewController(context: Context) -> AVPlayerViewController { | |
let item = AVPlayerItem(url: streamURL) | |
let groups = chapters.map { chapter -> AVTimedMetadataGroup in | |
let titleItem = AVMutableMetadataItem() | |
titleItem.identifier = .commonIdentifierTitle | |
titleItem.value = chapter.title as NSString | |
titleItem.extendedLanguageTag = "und" | |
var items: [AVMetadataItem] = [titleItem] | |
if let data = chapter.artworkData { | |
let artworkItem = AVMutableMetadataItem() | |
artworkItem.identifier = .commonIdentifierArtwork | |
artworkItem.dataType = kCMMetadataBaseDataType_PNG as String | |
artworkItem.value = data as NSData | |
artworkItem.extendedLanguageTag = "und" | |
items.append(artworkItem) | |
} | |
let time = CMTime(seconds: chapter.seconds, preferredTimescale: 1) | |
let range = CMTimeRange(start: time, duration: CMTime(seconds: 10, preferredTimescale: 1)) | |
return AVTimedMetadataGroup(items: items, timeRange: range) | |
} | |
let navGroup = AVNavigationMarkersGroup(title: "Chapters", timedNavigationMarkers: groups) | |
item.navigationMarkerGroups = [navGroup] | |
let player = AVPlayer(playerItem: item) | |
let controller = AVPlayerViewController() | |
controller.player = player | |
player.play() | |
return controller | |
} | |
func updateUIViewController(_ uiViewController: AVPlayerViewController, context: Context) {} | |
} | |
struct ContentView: View { | |
@StateObject var loader = ChaptersLoader() | |
let streamURL = URL(string: "https://ireplay.tv/test/blender.m3u8")! | |
var body: some View { | |
Group { | |
if loader.ready { | |
AVPlayerViewControllerRepresentable(streamURL: streamURL, chapters: loader.chapters) | |
.edgesIgnoringSafeArea(.all) | |
} else { | |
ProgressView("Loading cat artwork...") | |
.onAppear { loader.load() } | |
} | |
} | |
} | |
} | |
@main | |
struct YourProjectNameApp: App { | |
var body: some Scene { | |
WindowGroup { | |
ContentView() | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment