Skip links

How to video stream of IPTV with m3u8 parser in Swift

M3U File

m3u is a computer file format for a multimedia playlist. m3u8 includes album artisttrack informationdisplay 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

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
}
}
  • #EXTINF:“ word defines a playlist fileIt 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)
}
}
}
  • 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.

Leave a comment