DLNA 投网络上的媒体文件已经在前几篇实现过了。现在记录一下把本地图片和视频文件投到 DLNA 设备。
基础知识
DLNA
关于 DLNA 的基础知识请看一下四篇文章:
GCDWebServer
GCDWebServer 是一个现代化的轻量级的基 于HTTP 1.1 的 GCD server,它主要用于嵌入 OS X & iOS apps。GCDWebServer 在我们的实现中扮演 HTTP Server 的作用。使用前确保你已阅读一下两篇:
实现思路
目前我有两种思路
- 实现完整的 DLNA Media Server,提供媒体目录和存储。
- 跑一个 HTTP Server,产生文件 URL,然后把 URL 投到 DLNA 设备,相当于投网络视频。
其中方案1是 DLNA 标准的做法,方案2相当于是简化版的 DMS( DLNA Media Server)。考虑到我的 DLNA 实现全都是自己写的,我选择了方案2。
注:
- 相册 - iOS 系统相册
- DLNAMediaHelper - 我写的一个 Helper 单例,用于保存随机产生的 url 和对应的 PHAsset 资源、获取某个具体 URL 对应的媒体文件数据或者路径。
- DLNAManager - DLNA 投屏部分是实现类,用于处理 SSDP发现设备,发送 SOAP 命令以及接受订阅。
- DLNA 设备 - 目标 DLNA 设备
- Webserver - 本机(iphone)HTTP Server
具体实现过程
相册获取 PHAsset
从相册获取媒体需要熟悉 PhotoKit,具体不阐述了。此处我选择了能够获取图片和视频的第三方开源框架 CTAssetsPickerController
产生 URL,保存字典
其实这一步没什么难度,首先根据时间戳产生一个 URL,并把这个 URL 作为 key, asset 作为 value 存到一个字典内,用于后期处理请求即可。具体实现看下面。
投 URL 到 DLNA 设备
详见 基于 DLNA 实现 iOS,Android 投屏:SOAP 控制设备
获取 PHAsset 对应的资源文件
PHAsset 代表照片库中的一个资源,不是真正的原始数据。所以我们还需要使用 PHAsset 获取对应资源。具体的获取方法我均放在 DLNAMediaManager
类中。
DLNAMediaManager 实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
| class DLNAMediaManager { static let shared = DLNAMediaManager()
var mediaList: [String:PHAsset] = [:]
var serverURL: String { get { return DLNAManager.sharedManager.webServer.serverURL.URLString } }
func generateURL(forAsset :PHAsset) -> String { var url = "" if forAsset.mediaType == .Video { url = serverURL + "videos/" + UIUTil.gettimestampForNow().md5() + ".mov" } else { url = serverURL + "images/" + UIUTil.gettimestampForNow().md5() + ".jpg" }
mediaList[url] = forAsset return url }
func fetchImageData(url:String, callBack:((data:NSData?)->Void)) { if let asset = imageList[url] { let options = PHImageRequestOptions() options.synchronous = false options.deliveryMode = .HighQualityFormat options.networkAccessAllowed = true PHImageManager.defaultManager().requestImageForAsset(asset, targetSize: PHImageManagerMaximumSize, contentMode: .Default, options: options) { (result, info) -> Void in if let image = result, data = UIImageJPEGRepresentation(image, 1.0) { callBack(data:data) } else { callBack(data: nil) } } } else { callBack(data: nil) } }
func fetchVideoFile(url:String, callBack:((fileURL:String?)->Void)) {
if let asset = imageList[url] { let imageManager = PHImageManager.defaultManager() let videoRequestOptions = PHVideoRequestOptions()
videoRequestOptions.deliveryMode = .Automatic videoRequestOptions.version = .Current videoRequestOptions.networkAccessAllowed = true
imageManager.requestAVAssetForVideo(asset, options: videoRequestOptions, resultHandler: { (avAsset, avAudioMix, info) -> Void in
if let nextURLAsset = avAsset as? AVURLAsset, filepath = nextURLAsset.URL.path { callBack(fileURL: filepath) } else { callBack(fileURL: nil) } }) } else { return callBack(fileURL: nil) } } }
|
Webserver 接受请求
使用 GCD,具体处理方法如下,请注意其中图片和视频文件处理方法略有不同。关于 GCDWebServer 具体细节请见 GCDWebServer Readme
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| func startWebServer() {
let MediaNotFoundResponse = GCDWebServerResponse(statusCode:404)
webServer.addHandlerForMethod("GET", pathRegex: "/images/", requestClass: GCDWebServerRequest.self) { (request, completionBlock) in let url = request.URL.URLString DLNAImageHelper.fetchImageData(url, callBack: { (data) in if let data = data { let response = GCDWebServerDataResponse(data: data, contentType: "image/jpeg") completionBlock(response) } else { completionBlock(MediaNotFoundResponse) } }) }
webServer.addHandlerForMethod("GET", pathRegex: "/videos/", requestClass: GCDWebServerRequest.self) { (request, completionBlock) in let url = request.URL.URLString DLNAImageHelper.fetchVideoFile(url, callBack: { (fileURL) in if let file = fileURL { let response = GCDWebServerFileResponse(file: file, byteRange: request.byteRange) completionBlock(response) } else { completionBlock(MediaNotFoundResponse) } }) }
webServer.startWithPort(8899, bonjourName: nil) }
|
参考