We all know building an iOS application requires some UI and Swift knowledge. While creating many standard features, UI components are placed, then an interaction and business layer is created in accordance with the design pattern used. But there are some features that the algorithms should be used to play a key role. IPTV feature is one of them.
M3U File
m3u is a computer file format for a multimedia playlist. m3u8 includes album artist, track information, display title, etc…
#EXTM3U | |
#EXTINF:0, TRT1 | |
https://tv-trt1.medya.trt.com.tr/master_720.m3u8 | |
#EXTINF:0, TRT 1 HD | |
https://mn-nl.mncdn.com/blutv_trt12/live.m3u8 | |
#EXTINF:0, TRT 1 FHD | |
http://213.115.248.46:9000/TR_-_TRT_1_HD_PLUS/index.m3u8 | |
#EXTINF:0, ATV SD | |
http://azerbaijan1.livetv.az/turkey/atv_turk_sd/playlist.m3u8 | |
#EXTINF:0, ATV HD TR | |
https://trkvz-live.daioncdn.net/atv/atv_720p.m3u8 | |
#EXTINF:0, ATV FHD | |
http://213.115.248.46:9000/TR_-_ATV_HD_PLUS/index.m3u8 |
First of all, we need to grab, then map the m3u file into an array of objects.
Parser Logic
// | |
// m3u8Parse.swift | |
// M3U8ParseTutorial | |
// | |
// Created by Abdullah Yalçın on 6.07.2022. | |
// | |
import Foundation | |
struct MediaItem: Codable { | |
var duration: Int? | |
var title: String? | |
var urlString: String? | |
} | |
class ParseHelper { | |
func parseM3U(contentsOfFile: String) -> [MediaItem] { | |
var mediaItems = [MediaItem]() | |
contentsOfFile.enumerateLines(invoking: { line, stop in | |
if line.hasPrefix(“#EXTINF:“) { | |
let infoLine = line.replacingOccurrences(of: “#EXTINF:“, with: ““) | |
let infos = Array(infoLine.components(separatedBy: “,“)) | |
if let durationString = infos.first, let duration = Int(durationString) { | |
let mediaItem = MediaItem(duration: duration, title: infos.last?.trimmingCharacters(in: .whitespaces), urlString: nil) | |
mediaItems.append(mediaItem) | |
} | |
} else { | |
if mediaItems.count > 0 { | |
mediaItems[mediaItems.count – 1].urlString = line | |
} | |
} | |
}) | |
return mediaItems | |
} | |
} |
Here is a struct MediaItem, it includes streamUrl, title, and duration.
- parseM3U function gets an m3u file content, which is presented in String format.
- “#EXTINF:“ word defines a playlist file. It looks up line by line, then maps into MediaItem.
ViewController to stream
// | |
// IPTVViewController.swift | |
// avemobilecms | |
// | |
// Created by Abdullah Yalçın on 6.07.2022. | |
// | |
import Foundation | |
import AVKit | |
class IPTVViewController: UIViewController { | |
@IBOutlet private weak var listTableView: UITableView! | |
private var mediaList: [MediaItem] = [] | |
private let m3uFileLink = “myUrl.cdn.net/example.m3u“ | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
if let url = URL(string: m3uFileLink) { | |
do { | |
let fileContents = try String(contentsOf: url, encoding: .ascii) | |
mediaList = ParseHelper().parseM3U(contentsOfFile: fileContents) | |
listTableView.register(UITableViewCell.self, forCellReuseIdentifier: UITableViewCell.reuseIdentifier) | |
listTableView.delegate = self | |
listTableView.dataSource = self | |
}catch { | |
print(error) | |
} | |
} | |
} | |
private func executeUrl(row: Int) { | |
if let urlString = mediaList[row].urlString, let url = URL(string: urlString) { | |
let player = AVPlayer(url: url) | |
let controller = AVPlayerViewController() | |
controller.delegate = self | |
controller.player = player | |
present(controller, animated: true) { | |
player.play() | |
} | |
} | |
} | |
} | |
extension IPTVViewController: UITableViewDelegate, UITableViewDataSource { | |
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { | |
mediaList.count | |
} | |
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { | |
let cell = tableView.dequeueReusableCell(withIdentifier: UITableViewCell.reuseIdentifier, for: indexPath) | |
cell.textLabel?.text = mediaList[indexPath.row].title | |
return cell | |
} | |
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { | |
tableView.deselectRow(at: indexPath, animated: true) | |
executeUrl(row: indexPath.row) | |
} | |
} | |
extension IPTVViewController: AVPlayerViewControllerDelegate { | |
func playerViewController(_ playerViewController: AVPlayerViewController, failedToStartPictureInPictureWithError error: Error) { | |
print(error) | |
} | |
func playerViewController(_ playerViewController: AVPlayerViewController, | |
restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void) { | |
present(playerViewController, animated: false) { | |
completionHandler(true) | |
} | |
} | |
} |
- In this example, we grabbed an m3u file from a remote CDN provider. You can store it in a string or internal storage of your application.
- Build a listTableView to present media.
- AVKit can get a stream URL directly, which is built-in.
- AVPlayerViewControllerDelegate is used for Picture-in-Picture option.
In order to activate Picture-in-Picture in your app
- Enable “Audio, Airplay, and Picture in Picture” mode in your app target.
- In your appDelegate -> didFinishLaunchingWithOptions function, set category and activate like below.
do {
try AVAudioSession.sharedInstance().setCategory(.playback)
try AVAudioSession.sharedInstance().setActive(true)
}catch {
print(error)
}