Crafting the Ultimate iOS Video Player: Part 3— Exploring Video Quality Selection
In the third article of our Crafting the Ultimate iOS Video Player series, we will explore the intricacies of video quality settings and resolution selection. By the end of this article, you’ll gain the knowledge and techniques to efficiently manage the various quality resolutions supported by the video stream URL. This skill will empower you to provide your users with the best possible video quality and a seamless viewing experience.
Before we dive into integrating video quality selection, make sure to check out Part 2 of Crafting the Ultimate iOS Video Player series — Demystifying Subtitle Handling if you haven’t already.
Fetching Supported Video Qualities
In this section, we will delve into how to fetch all the supported video qualities provided by an M3u8 stream. To achieve this, we’ll use a M3u8Helper class, which contains methods for handling the M3u8 manifest and extracting video quality information.
The M3u8Helper processes the M3u8 manifest data and retrieves video quality information such as bitrate and resolution. It then organizes and sorts these qualities, providing an option for “Auto” quality selection for a seamless viewing experience. This approach allows you to provide your users with the ability to choose the desired video quality based on their preferences.
struct VideoQuality {
let bitrate: Double
let resolution: String
}
....
class M3u8Helper {
private enum Constants {
static let bandwidth = "BANDWIDTH"
static let resolution = "RESOLUTION"
}
private var qualities: [VideoQuality] = []
func fetchSupportedVideoQualities(with data: Data) -> [VideoQuality] {
handleManifest(data: data)
qualities.sortAndInsertAutoVideoQualityOption()
return qualities
}
private func handleManifest(data: Data) {
if let stringData = String(data: data, encoding: .utf8) {
qualities = parse(stringData: stringData)
}
}
private func parse(stringData: String) -> [VideoQuality] {
var result: [VideoQuality] = []
let rows = stringData.components(separatedBy: "\n")
for row in rows {
if let quality = quality(from: row) {
if let index = result.firstIndex(where: { $0.resolution == quality.resolution }) {
if result[index].bitrate < quality.bitrate {
result.remove(at: index)
result.append(quality)
}
} else {
result.append(quality)
}
}
}
return result
}
private func quality(from segments: String) -> VideoQuality? {
let dataSegments = segments.components(separatedBy: ",")
if let bandwidthSegments = dataSegments.first(where: { $0.contains(Constants.bandwidth) }),
let resolutionSegments = dataSegments.first(where: { $0.contains(Constants.resolution) })
{
let bandwidth = bandwidthSegments.components(separatedBy: "=")
let resolution = resolutionSegments.components(separatedBy: "=")
if bandwidth.count > 1, resolution.count > 1, let bitrate = Double(bandwidth[1]), let resolution = prettyResolution(from: resolution[1]) {
return VideoQuality(bitrate: bitrate, resolution: resolution)
}
}
return nil
}
private func prettyResolution(from resolution: String) -> String? {
let resolutionSegments = resolution.lowercased().components(separatedBy: "x")
if resolutionSegments.count > 1 {
return resolutionSegments[1] + "p"
}
return nil
}
}
Here’s an overview of the flow to fetch supported video qualities from an M3u8 URL:
- HTTP Request: You initiate an HTTP request to the M3u8 URL to fetch the manifest data.
- Receive Data: Once the request is successful, you receive the data from the M3u8 stream.
- Data to String: The received data is then converted into a string representation. This string data contains information about different video qualities.
- Parsing: The `M3u8Helper` class is used to parse this string data. It extracts video quality information like bandwidth and resolution from each line in the manifest.
- Quality Sorting: The parsed qualities are organized and sorted. The `sortAndInsertAutoVideoQualityOption` method sorts the qualities based on bitrate, ensuring that the highest quality is at the top. It also inserts an “Auto” quality option to let users choose the best quality automatically.
Finally, the sorted video qualities are returned, ready to be presented to the user for selection. This flow allows you to efficiently fetch and present the available video qualities to your users, giving them the flexibility to choose the quality that suits their network conditions and preferences.
#EXTM3U
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="stereo",LANGUAGE="en",NAME="English",DEFAULT=YES,AUTOSELECT=YES,URI="audio/stereo/en/128kbit.m3u8"
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="stereo",LANGUAGE="dubbing",NAME="Dubbing",DEFAULT=NO,AUTOSELECT=YES,URI="audio/stereo/none/128kbit.m3u8"
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="surround",LANGUAGE="en",NAME="English",DEFAULT=YES,AUTOSELECT=YES,URI="audio/surround/en/320kbit.m3u8"
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="surround",LANGUAGE="dubbing",NAME="Dubbing",DEFAULT=NO,AUTOSELECT=YES,URI="audio/stereo/none/128kbit.m3u8"
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="Deutsch",DEFAULT=NO,AUTOSELECT=YES,FORCED=NO,LANGUAGE="de",URI="subtitles_de.m3u8"
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="English",DEFAULT=YES,AUTOSELECT=YES,FORCED=NO,LANGUAGE="en",URI="subtitles_en.m3u8"
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="Espanol",DEFAULT=NO,AUTOSELECT=YES,FORCED=NO,LANGUAGE="es",URI="subtitles_es.m3u8"
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="Français",DEFAULT=NO,AUTOSELECT=YES,FORCED=NO,LANGUAGE="fr",URI="subtitles_fr.m3u8"
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=258157,CODECS="avc1.4d400d,mp4a.40.2",AUDIO="stereo",RESOLUTION=422x180,SUBTITLES="subs"
video/250kbit.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=520929,CODECS="avc1.4d4015,mp4a.40.2",AUDIO="stereo",RESOLUTION=638x272,SUBTITLES="subs"
video/500kbit.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=831270,CODECS="avc1.4d4015,mp4a.40.2",AUDIO="stereo",RESOLUTION=638x272,SUBTITLES="subs"
video/800kbit.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1144430,CODECS="avc1.4d401f,mp4a.40.2",AUDIO="surround",RESOLUTION=958x408,SUBTITLES="subs"
video/1100kbit.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1558322,CODECS="avc1.4d401f,mp4a.40.2",AUDIO="surround",RESOLUTION=1277x554,SUBTITLES="subs"
video/1500kbit.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=4149264,CODECS="avc1.4d4028,mp4a.40.2",AUDIO="surround",RESOLUTION=1921x818,SUBTITLES="subs"
video/4000kbit.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=6214307,CODECS="avc1.4d4028,mp4a.40.2",AUDIO="surround",RESOLUTION=1921x818,SUBTITLES="subs"
video/6000kbit.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=10285391,CODECS="avc1.4d4033,mp4a.40.2",AUDIO="surround",RESOLUTION=4096x1744,SUBTITLES="subs"
video/10000kbit.m3u8
The above is the string data which has the necessary video quality information supported by the stream — https://bitmovin-a.akamaihd.net/content/sintel/hls/playlist.m3u8
Note: In the context of AVPlayer, Auto video quality means that the player dynamically adjusts the video resolution based on the viewer’s internet bandwidth, aiming to provide the best quality without interruptions, all while maximizing the available network resources.
ViewModel:
In the ViewModel, we have the following functions and actions related to video quality:
- fetchSupportedVideoQualities(): This function is responsible for fetching the supported video qualities for the current video. It does this by using the `M3u8Helper` class to parse the manifest file (M3U8) for the video URL. The parsed video qualities are stored in the `playbackQualities` property, and the delegate is notified upon successful or failed completion.
- fetchPlaybackBitrate(for index: Int) -> Double?: This function is used to retrieve the bitrate of a specific video quality at a given index in the playbackQualities. The index represents the selected video quality, and this function returns the bitrate associated with that quality.
These functions allow the ViewModel to fetch and manage the available video quality options, making it possible to switch between different quality settings during video playback.
The selection of video quality settings can be implemented based on the design requirements and user preferences to provide a tailored viewing experience.
Link to Github repo — https://github.com/ajkmr7/Custom-Video-Player
I hope the knowledge shared in this article will prove instrumental in enhancing your iOS app’s video quality selection features, thereby improving user satisfaction and engagement.
Check out Part 4 of Crafting the Ultimate iOS Video Player — Elevating Your Player with Live Content Support
Stay connected for upcoming articles within this series as we delve into more advanced video player features. Happy coding! ⚡