You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift应用中M3U8视频无法播放(播放按钮禁用)

问题分析与解决方案

核心问题原因

  1. Content-Type 不匹配:服务器返回的 M3U8 文件使用了 text/plain 类型头,而非流媒体标准的 application/vnd.apple.mpegurl 或 application/x-mpegURL,导致 AVPlayer 无法识别为可播放的流媒体资源。
  2. 访问权限限制:服务器可能校验请求头(如 User-Agent、Referer)或 Cookie,播放器默认请求被拒绝,无法获取有效播放列表。
  3. M3U8 内容无效:播放列表本身未包含有效媒体分片链接,或分片链接同样存在访问限制。

分步解决方法

1. 排查服务器响应头

先确认服务器返回的 Content-Type 是否正确,在终端执行:

curl -I "https://video-m2se42aed.msigma.in/video/7282/0/1094/6498c142878c493e93d0e6a90312d6ea/ece_nt_bombay_1/master.m3u8?sd=10&b=1200&rebase=on"

如果返回的 Content-Type 是 text/plain,需联系服务器管理员修改配置,设置为流媒体标准类型。

2. 监听加载错误定位具体问题

修改你的 SwiftUI 代码,添加 AVPlayerItem 状态监听,获取具体错误信息:

import SwiftUI
import AVKit
import AVFoundation

struct MyView: UIViewControllerRepresentable {
    
    var videoURLString: String

    typealias UIViewControllerType = AVPlayerViewController
    
    func makeUIViewController(context: Context) -> AVPlayerViewController {
        let playerViewController = AVPlayerViewController()
        guard let url = URL(string: videoURLString) else {
            return playerViewController
        }
        
        let asset = AVURLAsset(url: url)
        let playerItem = AVPlayerItem(asset: asset)
        playerItem.addObserver(context.coordinator, forKeyPath: "status", options: [.new], context: nil)
        
        let player = AVPlayer(playerItem: playerItem)
        playerViewController.player = player
        return playerViewController
    }
    
    func updateUIViewController(_ uiViewController: AVPlayerViewController, context: Context) {}
    
    func makeCoordinator() -> Coordinator {
        Coordinator(parent: self)
    }
    
    class Coordinator: NSObject {
        var parent: MyView
        
        init(parent: MyView) {
            self.parent = parent
        }
        
        override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
            guard let playerItem = object as? AVPlayerItem, keyPath == "status" else {
                super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
                return
            }
            
            switch playerItem.status {
            case .failed:
                print("加载失败: \(playerItem.error?.localizedDescription ?? "未知错误")")
            case .readyToPlay:
                print("准备播放")
            case .unknown:
                print("状态未知")
            @unknown default:
                break
            }
        }
    }
}

运行后查看控制台输出,根据错误信息针对性解决(如权限问题、资源不存在等)。

3. 自定义 URLProtocol 修正请求/响应

如果无法修改服务器配置,可通过自定义 URLProtocol 拦截请求,添加必要头信息并修正 Content-Type:

class CustomM3U8Protocol: URLProtocol {
    override class func canInit(with request: URLRequest) -> Bool {
        return request.url?.absoluteString.contains(".m3u8") ?? false
    }
    
    override class func canonicalRequest(for request: URLRequest) -> URLRequest {
        return request
    }
    
    override func startLoading() {
        guard let url = request.url else {
            client?.urlProtocol(self, didFailWithError: NSError(domain: "CustomM3U8Protocol", code: -1, userInfo: [NSLocalizedDescriptionKey: "无效URL"]))
            return
        }
        
        // 模拟浏览器请求头,绕过服务器校验
        var mutableRequest = request
        mutableRequest.setValue("Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1", forHTTPHeaderField: "User-Agent")
        
        let task = URLSession.shared.dataTask(with: mutableRequest) { [weak self] data, response, error in
            guard let self = self else { return }
            
            if let error = error {
                self.client?.urlProtocol(self, didFailWithError: error)
                return
            }
            
            guard let originalResponse = response as? HTTPURLResponse, let data = data else {
                self.client?.urlProtocol(self, didFailWithError: NSError(domain: "CustomM3U8Protocol", code: -2, userInfo: [NSLocalizedDescriptionKey: "无响应数据"]))
                return
            }
            
            // 修正 Content-Type 为流媒体标准类型
            let headers = originalResponse.allHeaderFields as? [String: String] ?? [:]
            var newHeaders = headers
            newHeaders["Content-Type"] = "application/vnd.apple.mpegurl"
            
            guard let newResponse = HTTPURLResponse(url: url, statusCode: originalResponse.statusCode, httpVersion: "HTTP/1.1", headerFields: newHeaders) else {
                self.client?.urlProtocol(self, didFailWithError: NSError(domain: "CustomM3U8Protocol", code: -3, userInfo: [NSLocalizedDescriptionKey: "无法创建新响应"]))
                return
            }
            
            self.client?.urlProtocol(self, didReceive: newResponse, cacheStoragePolicy: .notAllowed)
            self.client?.urlProtocol(self, didLoad: data)
            self.client?.urlProtocolDidFinishLoading(self)
        }
        task.resume()
    }
    
    override func stopLoading() {}
}

在 App 启动时注册该协议:

@main
struct YourApp: App {
    init() {
        URLProtocol.registerClass(CustomM3U8Protocol.self)
    }
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

4. 检查 ATS 配置

如果链接涉及 HTTP 资源(或存在跨域问题),在 Info.plist 中添加 ATS 例外配置:

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSExceptionDomains</key>
    <dict>
        <key>video-m2se42aed.msigma.in</key>
        <dict>
            <key>NSIncludesSubdomains</key>
            <true/>
            <key>NSAllowsArbitraryLoads</key>
            <true/>
        </dict>
    </dict>
</dict>

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 06:31:12