Skip to content

Theorhd/TSPlayerKit

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 

Repository files navigation

TSPlayerKit

Swift 6.3 Platforms SPM

Lire des fichiers vidéo .ts (Transport Stream) avec AVPlayer, sans transcodage ni dépendance externe. Compatible iOS 17+.


Problème résolu

Apple bloque nativement la lecture des fichiers .ts bruts par AVFoundation en dehors d'une structure HLS (.m3u8). Les approches classiques imposent :

  • L'intégration d'outils C pour remuxer la vidéo en .mp4 avant lecture
  • Des dépendances lourdes type ffmpeg
  • Une complexité qui alourdit le binaire et le code

TSPlayerKit élimine cette contrainte avec une approche 100% Swift, sans dépendance, en simulant un flux HLS virtuel servi directement depuis le disque.


Comment ça marche

Note iOS 17+ — Depuis iOS 17, le moteur HLS d'Apple rejette le chargement manuel des segments via AVAssetResourceLoaderDelegate. TSPlayerKit utilise désormais un serveur HTTP local (basé sur le framework natif Network) — zéro dépendance externe.

┌─────────────────────────────────────────────────────┐
│                    AVPlayer                          │
│  "Je veux lire http://127.0.0.1:[port]/playlist.m3u8"│
└──────────────┬──────────────────────────────────────┘
               │ ① Requête HTTP GET standard
               ▼
┌─────────────────────────────────────────────────────┐
│          LocalHTTPServer (NWListener)                │
│  Route GET /playlist.m3u8                           │
│  → Retourne le .m3u8 en mémoire (200 OK)            │
└──────────────┬──────────────────────────────────────┘
               │ ② AVPlayer demande le segment TS via HTTP
               ▼
┌─────────────────────────────────────────────────────┐
│          LocalHTTPServer (NWListener)                │
│  Route GET /segment.ts                              │
│  → Parse le Header "Range: bytes=start-end"         │
│  → FileStreamer.readBytes(offset:, length:)          │
│  → Retourne 206 Partial Content avec les octets     │
└─────────────────────────────────────────────────────┘
  1. Serveur HTTP localLocalHTTPServer démarre sur un port loopback aléatoire via NWListener (framework Network, sans dépendance)
  2. Playlist HLS virtuelle — Générée en mémoire, elle déclare le .ts comme unique segment via http://127.0.0.1:[port]/segment.ts
  3. Lecture disque à la voléeFileStreamer lit les plages d'octets demandées par les requêtes Range HTTP, avec support complet du seek

Installation

Swift Package Manager

Dans ton Package.swift :

dependencies: [
    .package(url: "https://github.com/Theorhd/TSPlayerKit.git", from: "1.0.0"),
],
targets: [
    .target(
        name: "MyApp",
        dependencies: ["TSPlayerKit"]
    ),
]

Ou dans Xcode : File → Add Packages… → colle l'URL du dépôt.


Utilisation

Lecture basique

import TSPlayerKit
import AVFoundation

// 1. Créer un TSPlayerItem depuis un fichier local .ts
let tsFileURL = Bundle.main.url(forResource: "video", withExtension: "ts")!
let tsItem = try TSPlayerItem(tsFileURL: tsFileURL)

// 2. Créer l'AVPlayer et lire
let player = AVPlayer(playerItem: tsItem.playerItem)
player.play()

Dans SwiftUI

import SwiftUI
import AVKit
import TSPlayerKit

struct VideoPlayerView: View {
    let tsFileURL: URL

    var body: some View {
        if let tsItem = try? TSPlayerItem(tsFileURL: tsFileURL) {
            VideoPlayer(player: AVPlayer(playerItem: tsItem.playerItem))
        } else {
            ContentUnavailableView(
                "Impossible de lire la vidéo",
                systemImage: "video.slash",
                description: Text("Le fichier .ts n'a pas pu être ouvert.")
            )
        }
    }
}

Avec UIKit (AVPlayerViewController)

import UIKit
import AVKit
import TSPlayerKit

func playVideo(from tsURL: URL) throws {
    let tsItem = try TSPlayerItem(tsFileURL: tsURL)
    let player = AVPlayer(playerItem: tsItem.playerItem)

    let controller = AVPlayerViewController()
    controller.player = player

    present(controller, animated: true) {
        player.play()
    }
}

Ajuster la durée cible du segment

// Pour une vidéo de 2 heures, augmente le targetDuration
// pour que la playlist reflète une durée plus réaliste
let tsItem = try TSPlayerItem(
    tsFileURL: videoURL,
    targetDuration: 7200.0  // 2 heures en secondes
)

Téléchargement + lecture

func downloadAndPlay(from remoteURL: URL) async throws {
    // Télécharge le .ts localement
    let (localURL, _) = try await URLSession.shared.download(from: remoteURL)

    // Crée le player item
    let tsItem = try TSPlayerItem(tsFileURL: localURL)

    // Joue sur le thread principal
    await MainActor.run {
        let player = AVPlayer(playerItem: tsItem.playerItem)
        player.play()
    }
}

API Reference

TSPlayerItem

Point d'entrée principal. Wrapper qui produit un AVPlayerItem configuré pour la lecture d'un fichier .ts.

public struct TSPlayerItem {
    /// L'AVPlayerItem prêt à être lu par AVPlayer.
    public let playerItem: AVPlayerItem

    /// Crée un player item pour un fichier .ts local.
    /// - Parameters:
    ///   - tsFileURL: L'URL locale du fichier .ts
    ///   - targetDuration: Durée déclarée dans le manifeste HLS (secondes, défaut: 10)
    /// - Throws: FileStreamerError si le fichier est inaccessible
    public init(tsFileURL: URL, targetDuration: Double = 10.0) throws
}

FileStreamerError

Erreurs pouvant survenir lors de la lecture du fichier.

Cas Description
.cannotOpenFile(URL) Le fichier .ts n'existe pas ou est inaccessible en lecture
.systemError(Error) Erreur système sous-jacente (permissions, E/S)
.offsetOutOfRange(offset:fileSize:) L'offset demandé dépasse la taille du fichier
.deinitialized Le streamer a été désalloué pendant une opération

Structure du package

Sources/TSPlayerKit/
├── TSPlayerItem.swift              ← API publique (wrapper)
├── LocalHTTPServer.swift           ← Serveur HTTP local (NWListener) — iOS 17+
├── HLSManifestGenerator.swift      ← Génération du .m3u8 virtuel
└── FileStreamer.swift              ← Lecture disque asynchrone (FileHandle)
Composant Rôle
TSPlayerItem Wrapper qui démarre le serveur, génère le manifest et assemble l'AVPlayerItem
LocalHTTPServer Serveur HTTP local basé sur NWListener : sert le manifest et les segments TS via HTTP standard
HLSManifestGenerator Génère la chaîne .m3u8 avec les URLs http://127.0.0.1:[port]/...
FileStreamer Lecture thread-safe du fichier via FileHandle, avec support byte-range pour le seek

Requirements

Plateforme Version minimum
macOS 10.15+
iOS 13.0+ (compatible iOS 17+)
tvOS 13.0+
visionOS 1.0+
Swift 6.3+
Frameworks AVFoundation, Foundation, Network

Limitations

  • Un seul segment — La playlist ne déclare qu'un seul segment TS. Pour des vidéos multi-segments, le manifeste devrait être enrichi.
  • Pas de chiffrement — Les fichiers doivent être en clair (pas de FairPlay DRM).
  • Performances disqueFileHandle lit de manière synchrone sur une queue dédiée. Pour des fichiers très volumineux (>10 Go), le seek peut introduire une latence perceptible.
  • Codecs supportés — Dépend des capacités d'AVFoundation sur l'appareil. Les codecs non supportés par la plateforme ne seront pas lus.

License

MIT — voir le fichier LICENSE.


Contributing

Les contributions sont les bienvenues. Ouvre une issue pour discuter de ce que tu souhaites changer avant de soumettre une PR.

  1. Fork le dépôt
  2. Crée une branche (git checkout -b feature/ma-fonctionnalite)
  3. Commit tes changements
  4. Push et ouvre une Pull Request

Construit avec ❤️ pour la communauté Apple — zéro dépendance, 100% Swift.

About

A native, zero-dependency .ts video player for iOS

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages