Flutter—iOS關(guān)于m3u8格式文件本地緩存與播放

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);
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市膳汪,隨后出現(xiàn)的幾起案子唯蝶,更是在濱河造成了極大的恐慌,老刑警劉巖遗嗽,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件粘我,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡痹换,警方通過(guò)查閱死者的電腦和手機(jī)征字,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)娇豫,“玉大人匙姜,你說(shuō)我怎么就攤上這事》肓。” “怎么了氮昧?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)浦楣。 經(jīng)常有香客問(wèn)我袖肥,道長(zhǎng),這世上最難降的妖魔是什么振劳? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任椎组,我火速辦了婚禮,結(jié)果婚禮上历恐,老公的妹妹穿的比我還像新娘寸癌。我一直安慰自己专筷,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布蒸苇。 她就那樣靜靜地躺著磷蛹,像睡著了一般。 火紅的嫁衣襯著肌膚如雪填渠。 梳的紋絲不亂的頭發(fā)上弦聂,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天鸟辅,我揣著相機(jī)與錄音氛什,去河邊找鬼。 笑死匪凉,一個(gè)胖子當(dāng)著我的面吹牛枪眉,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播再层,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼贸铜,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了聂受?” 一聲冷哼從身側(cè)響起蒿秦,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蛋济,沒(méi)想到半個(gè)月后棍鳖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡碗旅,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年渡处,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片祟辟。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡医瘫,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出旧困,到底是詐尸還是另有隱情醇份,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布吼具,位于F島的核電站僚纷,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏馍悟。R本人自食惡果不足惜畔濒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望锣咒。 院中可真熱鬧侵状,春花似錦赞弥、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至艇潭,卻和暖如春拼窥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蹋凝。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工鲁纠, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人鳍寂。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓改含,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親迄汛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子捍壤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355