You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

SwiftUI应用集成HTTP Live Streaming直播流播放技术咨询

嘿,我刚好帮好几个开发者搞定过SwiftUI里集成HLS直播流的需求,结合你已经会本地视频播放的基础,给你两个实用方案,还有一些优化细节,一步步来:

方案1:用SwiftUI原生VideoPlayer(iOS 14+)

这个是最省心的方案,苹果在iOS14之后给SwiftUI提供了原生的VideoPlayer视图,直接对接AVPlayer就能播放HLS直播流(.m3u8格式),完全不需要桥接UIKit。

完整代码示例

import SwiftUI
import AVKit

// 用ViewModel管理AVPlayer的生命周期,避免内存泄漏
class LiveStreamViewModel: ObservableObject {
    let player: AVPlayer
    
    init(streamURL: URL) {
        // 直接传入直播流的m3u8地址初始化AVPlayer
        self.player = AVPlayer(url: streamURL)
    }
    
    // 销毁时清理资源
    deinit {
        player.pause()
        player.replaceCurrentItem(with: nil)
    }
}

struct LiveStreamView: View {
    @StateObject private var viewModel: LiveStreamViewModel
    
    init(streamURL: URL) {
        _viewModel = StateObject(wrappedValue: LiveStreamViewModel(streamURL: streamURL))
    }
    
    var body: some View {
        VideoPlayer(player: viewModel.player)
            .onAppear {
                // 页面出现时开始播放
                viewModel.player.play()
            }
            .onDisappear {
                // 页面消失时暂停,节省资源
                viewModel.player.pause()
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .edgesIgnoringSafeArea(.all)
    }
}

// 使用示例
struct ContentView: View {
    // 替换成你的HLS直播流地址
    let liveStreamURL = URL(string: "https://your-stream-url.m3u8")!
    
    var body: some View {
        LiveStreamView(streamURL: liveStreamURL)
    }
}

方案优势

  • 完全SwiftUI原生写法,代码简洁
  • 自动适配SwiftUI的生命周期,减少手动管理的麻烦
  • 支持iOS14及以上系统,满足大部分现代项目需求
方案2:兼容iOS13的UIKit桥接方案

如果你的项目需要支持iOS13,就得用UIViewRepresentable把UIKit的AVPlayerViewController包装成SwiftUI视图。这个方案功能更全,比如画中画、自定义播放控制栏等都能轻松实现。

完整代码示例

import SwiftUI
import AVKit

// 把AVPlayerViewController包装成SwiftUI视图
struct AVPlayerView: UIViewControllerRepresentable {
    let player: AVPlayer
    
    func makeUIViewController(context: Context) -> AVPlayerViewController {
        let controller = AVPlayerViewController()
        controller.player = player
        controller.showsPlaybackControls = true // 可以设置是否显示原生控制栏
        controller.allowsPictureInPicturePlayback = true // 开启画中画支持
        return controller
    }
    
    func updateUIViewController(_ uiViewController: AVPlayerViewController, context: Context) {
        // 不需要额外更新逻辑,除非有状态变化
    }
}

// 同样用ViewModel管理AVPlayer
class LiveStreamViewModel: ObservableObject {
    let player: AVPlayer
    
    init(streamURL: URL) {
        self.player = AVPlayer(url: streamURL)
    }
    
    func play() {
        player.play()
    }
    
    func pause() {
        player.pause()
    }
}

struct LiveStreamView: View {
    @StateObject private var viewModel: LiveStreamViewModel
    
    init(streamURL: URL) {
        _viewModel = StateObject(wrappedValue: LiveStreamViewModel(streamURL: streamURL))
    }
    
    var body: some View {
        AVPlayerView(player: viewModel.player)
            .onAppear {
                viewModel.play()
            }
            .onDisappear {
                viewModel.pause()
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .edgesIgnoringSafeArea(.all)
    }
}

方案优势

  • 兼容iOS13及以上系统
  • 可以利用UIKit中AVPlayerViewController的全部功能,比如画中画、播放速度调节等
额外优化:提升直播体验的细节

不管用哪个方案,都建议加上这些细节,让用户体验更好:

  • 加载状态与错误处理:监听AVPlayer的状态,在加载时显示进度条,加载失败时提示用户重新加载

    // 在LiveStreamViewModel中添加状态监听
    class LiveStreamViewModel: ObservableObject {
        let player: AVPlayer
        @Published var playerStatus: AVPlayer.Status = .unknown
        
        init(streamURL: URL) {
            let playerItem = AVPlayerItem(url: streamURL)
            self.player = AVPlayer(playerItem: playerItem)
            
            // 监听播放项的状态变化
            playerItem.addObserver(self, forKeyPath: #keyPath(AVPlayerItem.status), options: [.old, .new], context: nil)
        }
        
        override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
            if keyPath == #keyPath(AVPlayerItem.status), let playerItem = object as? AVPlayerItem {
                DispatchQueue.main.async {
                    self.playerStatus = playerItem.status
                    if playerItem.status == .readyToPlay {
                        self.player.play()
                    }
                }
            }
        }
        
        deinit {
            player.currentItem?.removeObserver(self, forKeyPath: #keyPath(AVPlayerItem.status))
            player.pause()
            player.replaceCurrentItem(with: nil)
        }
    }
    

    然后在View中根据状态显示不同内容:

    var body: some View {
        ZStack {
            VideoPlayer(player: viewModel.player)
                .frame(maxWidth: .infinity, maxHeight: .infinity)
            
            if viewModel.playerStatus == .unknown {
                ProgressView("加载直播流...")
                    .foregroundColor(.white)
                    .padding()
                    .background(Color.black.opacity(0.5))
                    .cornerRadius(8)
            } else if viewModel.playerStatus == .failed {
                VStack {
                    Text("直播流加载失败")
                        .foregroundColor(.white)
                    Button("重新加载") {
                        let newItem = AVPlayerItem(url: streamURL)
                        viewModel.player.replaceCurrentItem(with: newItem)
                    }
                    .foregroundColor(.blue)
                    .padding()
                }
                .padding()
                .background(Color.black.opacity(0.7))
                .cornerRadius(12)
            }
        }
        .edgesIgnoringSafeArea(.all)
        .onDisappear {
            viewModel.player.pause()
        }
    }
    
  • 画中画权限:如果需要画中画功能,记得在Info.plist中添加NSPictureInPictureUsageDescription字段,填写权限说明(比如"需要画中画权限以继续观看直播")

  • 格式注意:确保你的直播流是标准的HLS格式(.m3u8后缀),AVPlayer原生支持这种格式,不需要额外引入第三方库

内容的提问来源于stack exchange,提问作者devo

火山引擎 最新活动