flutter處理m3u8格式流文件的本地存儲(chǔ)與本地播放抑进,flutter插件管理中暫無(wú)此塊處理的插件戈泼,Android的插件我在GitHub上找到一個(gè),親測(cè)可用眼滤!地址如下: https://github.com/lytian/m3u8_downloader巴席,安裝使用可查看其github
m3u8_downloader:
git:
url: https://github.com/lytian/m3u8_downloader.git
iOS處理使用交互處理,利用原生代碼實(shí)現(xiàn)下載緩存到本地诅需,在通過(guò)第三方庫(kù)-'CocoaHTTPServer' 開啟本地服務(wù)進(jìn)行訪問(wèn)本地文件實(shí)現(xiàn)播放漾唉。
flutter中iOS部分代碼為Swift 5.0
1、使用到的第三方庫(kù)堰塌,以下是第三方podfile庫(kù)配置:
platform :ios, '9.0' #手機(jī)的系統(tǒng)
use_frameworks!
# TestM3u8項(xiàng)目名稱
target 'TestM3u8' do
pod 'WLM3U' # m3u8格式的下載與組裝庫(kù)
pod 'Alamofire' # 網(wǎng)絡(luò)庫(kù)
pod 'CocoaHTTPServer' #配置本地服務(wù)庫(kù)赵刑,注意最低版本9.0不然可能會(huì)編譯報(bào)錯(cuò)
end
m3u8格式的下載與組裝庫(kù)地址:https://github.com/WillieWangWei/WLM3U 配置本地服務(wù):CocoaHTTPServer隨便一搜就會(huì)有很多, 下面也會(huì)有我的配置
2、由于我將WLM3U庫(kù)文件下載到本地了蔫仙,直接將其文件導(dǎo)入到項(xiàng)目中料睛,所以調(diào)用起方法不需要導(dǎo)入頭文件(import WLM3U),所以下載方法調(diào)用的時(shí)候?qū)?huì)是直接調(diào)用摇邦,如果是pod的可以參考這個(gè)庫(kù)提供的demo恤煞,使用他那樣的調(diào)用方式。
注意: iOS開發(fā)賬號(hào)要使用公司的或者自己能打包發(fā)布的賬號(hào)施籍,如果使用自己的開發(fā)者賬號(hào)(未充錢的)居扒,會(huì)出現(xiàn)無(wú)法下載成功!3笊鳌喜喂!
由于CocoaHTTPServer庫(kù)是Objective-C語(yǔ)言實(shí)現(xiàn)的瓤摧,需要在橋接文件中導(dǎo)入其頭文件(Runner-Bridging-Header.h)
#import "HTTPServer.h"
以下是iOS項(xiàng)目中AppDelegate文件中的配置(包括下載、播放路徑返回玉吁、本地服務(wù)配置)
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
var httpServer : HTTPServer! = nil // 本地服務(wù)
var isOpenServer : Bool! = false // 是否開啟服務(wù)
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
// 開啟本地服務(wù)
if (!isOpenServer) {
openServer()
}
//接收f(shuō)lutter發(fā)送來(lái)調(diào)用下載的消息(ios.download.file.m3u8)為消息名稱兩端必須一致
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
let batteryChannel = FlutterMethodChannel.init(name: "ios.download.file.m3u8", binaryMessenger: controller.binaryMessenger)
// downLoadFlie為監(jiān)聽調(diào)用的方法照弥,flutter端定義的,這邊判斷
batteryChannel.setMethodCallHandler({
[weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
guard call.method == "downLoadFlie" else {
result(FlutterMethodNotImplemented)
return
}
// 調(diào)用下載方法进副,call中包含傳遞過(guò)來(lái)的參數(shù)这揣,result回調(diào)
self?.downLoadFiles(call: call, result: result)
})
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
override func application(_ application: UIApplication, handleOpen url: URL) -> Bool {
return FlutterAlipayPlugin.handleOpen(url);
}
// 應(yīng)用程序被殺死進(jìn)程時(shí),關(guān)閉本地服務(wù)
override func applicationWillTerminate(_ application: UIApplication) {
if isOpenServer {
httpServer.stop()
}
}
// 開啟本地服務(wù)影斑,不設(shè)置端口给赞,由系統(tǒng)自動(dòng)分配
func openServer() -> Void {
httpServer = HTTPServer()
httpServer.setType("_http._tcp.")
// 播放沙盒文件
print("\(NSHomeDirectory())/Documents")
// 設(shè)置http服務(wù)器根目錄
httpServer.setDocumentRoot("\(NSHomeDirectory())/Documents")
do{
// 服務(wù)開啟成功
try httpServer.start()
isOpenServer = true
} catch {
isOpenServer = false
}
}
// 下載文件,文件存在返回本地播放地址
func downLoadFiles(call: FlutterMethodCall, result: FlutterResult) {
// 將call轉(zhuǎn)為字典進(jìn)行取出參數(shù)值
let tResult = call.arguments as![String:Any]
let url : String = tResult["playerURL"] as! String
let fileName : String = tResult["fileName"] as! String
// 校驗(yàn)文件名是否存在矫户,如果存在就直接返回本地文件訪問(wèn)地址
let b = filesIsExist(fileName)
if b {
// 本地服務(wù)啟動(dòng)成功返回本地服務(wù)地址片迅,啟動(dòng)失敗返回空字符串
// httpServer.listeningPort() 獲取參數(shù)啟動(dòng)的端口號(hào)
if isOpenServer {
result("http://127.0.0.1:\(httpServer.listeningPort())/WLM3U/\(fileName)/file.m3u8")
} else {
result("")
}
} else {
// 開線程執(zhí)行 進(jìn)行文件下載
DispatchQueue.global().async {
let url = URL(string: url)!
do {
let workflow = try attach(url: url,
calculateSize: true,
completion: { (result) in
switch result {
case .success(let model):
print("attach success " + model.name!)
case .failure(let error):
print("attach failure " + error.localizedDescription)
}
})
self.run(workflow: workflow)
} catch {
print(error.localizedDescription)
}
}
result("")
}
}
// 開始下載
func run(workflow: Workflow) {
workflow
.download(progress: { (progress, completedCount) in
print(Float(progress.fractionCompleted))
let mb = Double(completedCount) / 1024 / 1024
var text = ""
if mb >= 0.1 {
text = String(format: "%.1f", mb) + " M/s"
} else {
text = String(completedCount / 1024) + " K/s"
}
print(text)
}, completion: { (result) in
switch result {
case .success(let url):
print("download success " + url.path)
case .failure(let error):
print("download failure " + error.localizedDescription)
}
})
}
// 檢查文件地址是否存在
func filesIsExist(_ identifer: String) -> Bool {
let filePath = getDocumentsDirectory().appendingPathComponent("WLM3U").appendingPathComponent(identifer)
if FileManager.default.fileExists(atPath: filePath.path) {
let files = findFiles(path: filePath.path, filterTypes: ["m3u8"])
if files.count > 0 { // 本地m3u8文件已經(jīng)存在
print("文件已存在 -- filePath = \(filePath.path)")
return true
}
}
return false
}
func getDocumentsDirectory() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in:.userDomainMask)
let documentsDirectory = paths[0]
return documentsDirectory
}
func findFiles(path: String, filterTypes: [String]) -> [String] {
do {
let files = try FileManager.default.contentsOfDirectory(atPath: path)
if filterTypes.count == 0 {
return files
} else {
let filteredfiles = NSArray(array: files).pathsMatchingExtensions(filterTypes)
return filteredfiles
}
} catch {
return []
}
}
}
在調(diào)用頁(yè)面創(chuàng)建通信屬性
3、flutter調(diào)用下載方法進(jìn)行文件本地緩存
// 用于與iOS通信實(shí)現(xiàn)下載功能,name(ios.download.file.m3u8)唯一性皆辽,并且雙端一致
static const MethodChannel _methodChannel = const MethodChannel('ios.download.file.m3u8');
// 點(diǎn)擊事件調(diào)用下載功能
String fileName = '文件名稱';
String _playUrl = 'http://xxxxx.m3u8';
// downLoadFlie 為標(biāo)注要調(diào)用的方法柑蛇, 使用map進(jìn)行傳遞參數(shù)
try {
dynamic respose = await _methodChannel.invokeMethod('downLoadFlie', {'playerURL': _playUrl, 'fileName': fileName});
// 文件存在就返回本地播放地址
if (respose.toString().isNotEmpty) {
_playUrl = respose.toString();
}
} on PlatformException catch(e) {
print(e);
}