This is a simple iOS project that demonstrates how to handle video player states, download HLS live streams, and play them locally. The app includes:
- Player State Handling: Demonstrates managing player states like playing, paused, loading, and error.
- Custom Slider: Allows seeking through the video and displays the current playback time.
- Buffering: Shows real-time buffer progress with a customizable buffer duration.
- HLS Stream Download: Allows downloading HLS
.m3u8
streams for offline use, including both video and audio streams.
Playing and Buffering Process | Downloading Stream | Offline Playback |
- Clone this repository:
git clone https://github.com/nazar-41/Player41.git
- Open
Player41.xcodeproj
in Xcode. - Build and run on an iOS device or simulator.
The app uses an Enum_PlayerState
to manage the four playback states:
enum Enum_PlayerState {
case playing
case paused
case loading
case error(String)
}
The state is updated dynamically based on the AVPlayer
's status:
player?.publisher(for: \.timeControlStatus)
.sink { [weak self] status in
switch status {
case .playing:
self?.playerStatus = .playing
case .paused:
self?.playerStatus = .paused
case .waitingToPlayAtSpecifiedRate:
self?.playerStatus = .loading
@unknown default:
break
}
}
.store(in: &cancellables)
The app includes a slider to scrub through the video timeline. The slider reflects the currentTime
and totalDuration
dynamically:
Slider(
value: $viewModel.currentTime,
in: 0...(viewModel.totalDuration > 0 ? viewModel.totalDuration : 1),
onEditingChanged: { isDragging in
if isDragging {
viewModel.pause()
} else {
viewModel.seek(to: viewModel.currentTime)
viewModel.play()
}
}
)
The buffer progress is calculated using the loadedTimeRanges
of the player's currentItem
:
player?.publisher(for: \.currentItem?.loadedTimeRanges)
.sink { [weak self] timeRanges in
guard let self = self else { return }
self.updateBufferProgress(timeRanges)
}
.store(in: &cancellables)
private func updateBufferProgress(_ timeRanges: [NSValue]?) {
guard let timeRanges = timeRanges,
let firstRange = timeRanges.first?.timeRangeValue,
totalDuration > 0 else {
bufferProgress = 0
return
}
let bufferedTime = CMTimeGetSeconds(firstRange.start) + CMTimeGetSeconds(firstRange.duration)
bufferProgress = bufferedTime / totalDuration
}
The app supports downloading HLS streams using the following logic:
-
Start downloading a stream:
func downloadAsset(_ asset: Asset) { AssetPersistenceManager.sharedManager.downloadStream(asset.stream) }
-
Cancel a download:
func cancelDownload(_ asset: Asset) { AssetPersistenceManager.sharedManager.cancelDownload(for: asset.stream) }
-
Delete a downloaded stream:
func deleteAsset(_ asset: Asset) { AssetPersistenceManager.sharedManager.deleteAsset(asset) }
The UI provides a seamless way to download, cancel, and delete streams.
The download logic for HLS streams was taken from Apple's official example on how to handle media downloading in iOS apps. It demonstrates how to download, cache, and play HLS streams locally, and is implemented using AVAssetDownloadURLSession
for efficient downloading.
Developed by Nazar Velkakayev