Skip to content

Commit

Permalink
first version of auto-fused player item
Browse files Browse the repository at this point in the history
  • Loading branch information
alexeichhorn committed Oct 17, 2024
1 parent e43ad2a commit c82335e
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 0 deletions.
46 changes: 46 additions & 0 deletions Sources/YouTubeKit/YouTube+PlayerItem.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//
// YouTube+PlayerItem.swift
// YouTubeKit
//
// Created by Alexander Eichhorn on 15.10.2024.
//

import Foundation
import AVFoundation

@available(iOS 13.0, watchOS 6.0, tvOS 13.0, macOS 10.15, *)
extension YouTube {

/// Returns `AVPlayerItem` for the highest resolution stream that is natively playable with potentially audio and video automatically combined.
/// - Parameter maxResolution: The maximum resolution of the video stream. If `nil`, the highest resolution stream is used.
@MainActor
@available(iOS 15.0, watchOS 8.0, tvOS 15.0, macOS 12.0, *)
public func playerItem(maxResolution: Int? = nil) async throws -> AVPlayerItem {
let streams = try await streams

let composition = AVMutableComposition()

guard let videoStream = streams.filter({ $0.isNativelyPlayable }).filterVideoOnly().filter(byResolution: { ($0 ?? .max) <= (maxResolution ?? .max) }).highestResolutionStream(),
let audioStream = streams.filter({ $0.isNativelyPlayable }).filterAudioOnly().highestAudioBitrateStream() else {
throw YouTubeKitError.extractError
}

// Add video track
let videoAsset = AVURLAsset(url: videoStream.url)
let videoTrack = composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)
let videoAssetTrack = try await videoAsset.loadTracks(withMediaType: .video).first
let videoTimeRange = try await videoAsset.load(.duration)
try videoTrack?.insertTimeRange(CMTimeRange(start: .zero, duration: videoTimeRange), of: videoAssetTrack!, at: .zero)

// Add audio track
let audioAsset = AVURLAsset(url: audioStream.url)
let audioTrack = composition.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid)
let audioAssetTrack = try await audioAsset.load(.tracks).first { $0.mediaType == .audio }
let audioTimeRange = try await audioAsset.load(.duration)
try audioTrack?.insertTimeRange(CMTimeRange(start: .zero, duration: audioTimeRange), of: audioAssetTrack!, at: .zero)

let playerItem = AVPlayerItem(asset: composition)
return playerItem
}

}
18 changes: 18 additions & 0 deletions Tests/YouTubeKitTests/PlayabilityTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,22 @@ final class PlayabilityTests: XCTestCase {
}
}


func testAutoCombinedPlayerItemPlayability() async throws {

let videoID = "njX2bu-_Vw4"
let youtubeLocal = YouTube(videoID: videoID, methods: [.local])
let youtubeRemote = YouTube(videoID: videoID, methods: [.remote])

let localPlayerItem = try await youtubeLocal.playerItem()
let remotePlayerItem = try await youtubeRemote.playerItem()

let localIsPlayable = try await localPlayerItem.asset.load(.isPlayable)
let remoteIsPlayable = try await remotePlayerItem.asset.load(.isPlayable)

XCTAssert(localIsPlayable, "Local player item should be playable")
XCTAssert(remoteIsPlayable, "Remote player item should be playable")

}

}

0 comments on commit c82335e

Please sign in to comment.