思路
- 各大直播平台支持浏览器播放视频
- 播放的时候会把直播流地址暴露在标签里边, 绝大部分是在
<Video>
标签 - 如果不是
<Video>
标签怎么获得?
看几张网页截图
![](https://img.haomeiwen.com/i3753193/53d38706a77949b3.png)
![](https://img.haomeiwen.com/i3753193/cb7cc9f568f003e6.png)
![](https://img.haomeiwen.com/i3753193/1da22d2fdb0930a8.png)
移动端获得直播流地址
- 1, 获得网页端端直播H5地址, URL
- 2, 加载网页, 待网页加载到一定的时候获取
<Video>
标签的src
值 - 3, 如果不是
<Video>
标签, 根据标签的id
或者class
获得 src 值
关键步骤在于网页加载后获得直播流地址
document.querySelector() 不清楚的小伙伴百度下, 就不过多赘述了
通过标签id
获得直播流地址
webView.evaluateJavaScript("document.querySelector(\"#\(elementId)\").src") { (streamUrl, error) in
if let stream = streamUrl as? String, stream.lengthOfBytes(using: String.Encoding.utf8) > 0 {
self.successCallback(webView: webView, streamUrl: stream)
}
}
通过标签class
获得直播流地址
webView.evaluateJavaScript("document.querySelector(\".\(elementClass)\").src") { (streamUrl, error) in
if let stream = streamUrl as? String, stream.lengthOfBytes(using: String.Encoding.utf8) > 0 {
self.successCallback(webView: webView, streamUrl: stream)
}
}
通过标签video[src]
获得直播流地址
webView.evaluateJavaScript("document.querySelector(\"video[src]\").src") { (streamUrl, error) in
if let stream = streamUrl as? String, stream.lengthOfBytes(using: String.Encoding.utf8) > 0 {
self.successCallback(webView: webView, streamUrl: stream)
}
}
}
iOS 移动端获取直播流
工具类示例
WKWebview在这里只是一个工具, 并不展示!!
import WebKit
public enum NJLiveRoomStreamToolError: Error {
case timeout
case pageFail
}
public class NJLiveRoomStreamTool: NSObject {
public static var sharedTool: NJLiveRoomStreamTool = NJLiveRoomStreamTool()
private var handles = [WKWebView: [String: Any]]()
}
extension NJLiveRoomStreamTool {
/// 通过 h5Room链接 获取直播流地址, elementId和elementClass 只用填一个
///
/// - Parameters:
/// - roomH5Url: 直播h5网页链接
/// - elementId: 直播h5网页 里边<video>标签的 id
/// - elementClass: 直播h5网页 里边<video>标签的class
/// - success: 成功的回调
/// - failure: 失败的回调
public func nj_getStreamUrl(roomH5Url: String, elementId: String? = nil, elementClass: String? = nil, success: @escaping (_ roomH5Url: String, _ streamUrl: String) -> (), failure: @escaping (_ roomH5Url: String, _ error: Error) -> ()) {
let webView = addWkWebView()
setUpWkWebView(webView: webView)
handles[webView] = ["success": success, "failure": failure, "roomH5Url": roomH5Url, "elementId": elementId, "elementClass": elementClass]
if let url = URL(string: roomH5Url) {
let urlRequestM = NSMutableURLRequest(url: url)
webView.load(urlRequestM.copy() as! URLRequest)
}else {
self.failCallback(webView: webView, error: NJLiveRoomStreamToolError.pageFail)
}
let tinyDelay = DispatchTime.now() + Double(Int64(30 * Float(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)
DispatchQueue.main.asyncAfter(deadline: tinyDelay) {
self.failCallback(webView: webView, error: NJLiveRoomStreamToolError.timeout)
}
}
}
// MARK:- handle
extension NJLiveRoomStreamTool {
private func failCallback(webView: WKWebView, error: Error) {
if let fail = self.handles[webView]?["failure"] as? ((_ roomH5Url: String, _ error: Error) -> ()) {
fail(self.handles[webView]?["roomH5Url"] as! String, error)
webView.stopLoading()
self.handles.removeValue(forKey: webView)
}
}
private func successCallback(webView: WKWebView, streamUrl: String) {
if let success = self.handles[webView]?["success"] as? ((_ roomH5Url: String, _ streamUrl: String) -> ()) {
success(self.handles[webView]?["roomH5Url"] as! String, streamUrl)
webView.stopLoading()
self.handles.removeValue(forKey: webView)
}
}
// 通过js获得src
private func evaluateVideoElementJS(webView: WKWebView) {
if let elementId = self.handles[webView]?["elementId"] as? String {
webView.evaluateJavaScript("document.querySelector(\"#\(elementId)\").src") { (streamUrl, error) in
if let stream = streamUrl as? String, stream.lengthOfBytes(using: String.Encoding.utf8) > 0 {
self.successCallback(webView: webView, streamUrl: stream)
}
}
}
if let elementClass = self.handles[webView]?["elementClass"] as? String {
webView.evaluateJavaScript("document.querySelector(\".\(elementClass)\").src") { (streamUrl, error) in
if let stream = streamUrl as? String, stream.lengthOfBytes(using: String.Encoding.utf8) > 0 {
self.successCallback(webView: webView, streamUrl: stream)
}
}
}
webView.evaluateJavaScript("document.querySelector(\"video[src]\").src") { (streamUrl, error) in
if let stream = streamUrl as? String, stream.lengthOfBytes(using: String.Encoding.utf8) > 0 {
self.successCallback(webView: webView, streamUrl: stream)
}
}
}
}
// MARK:- setting
extension NJLiveRoomStreamTool {
private func addWkWebView() -> WKWebView {
let configuration = WKWebViewConfiguration()
let webView = WKWebView(frame: CGRect(x: 0, y: 0, width: 5, height: 5), configuration: configuration)
return webView
}
private func setUpWkWebView(webView: WKWebView?) -> Void {
let preferences = WKPreferences()
//是否支持JavaScript
preferences.javaScriptEnabled = true;
//不通过用户交互,是否可以打开窗口
preferences.javaScriptCanOpenWindowsAutomatically = true;
webView?.configuration.preferences = preferences
webView?.configuration.userContentController = WKUserContentController()
// 播放视频
webView?.configuration.allowsInlineMediaPlayback = true;
webView?.navigationDelegate = self;
}
}
// MARK:- WKNavigationDelegate-导航监听
extension NJLiveRoomStreamTool: WKNavigationDelegate {
// 1, 在发送请求之前,决定是否跳转
public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Swift.Void) {
decisionHandler(WKNavigationActionPolicy.allow)
}
// 3, 6, 加载 HTTPS 的链接,需要权限认证时调用 \ 如果 HTTPS 是用的证书在信任列表中这不要此代理方法
public func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Swift.Void) {
if let trust = challenge.protectionSpace.serverTrust {
let credential = URLCredential(trust: trust)
completionHandler(.useCredential, credential)
}else {
completionHandler(.performDefaultHandling, nil)
}
self.evaluateVideoElementJS(webView: webView)
}
// 4, 在收到响应后,决定是否跳转, 在收到响应后,决定是否跳转和发送请求之前那个允许配套使用
public func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Swift.Void) {
decisionHandler(WKNavigationResponsePolicy.allow)
}
//当 WKWebView 总体内存占用过大,页面即将白屏的时候,系统会调用回调函数
public func webViewWebContentProcessDidTerminate(_ webView: WKWebView) {
webView.reload()
self.failCallback(webView: webView, error: NJLiveRoomStreamToolError.pageFail)
}
}
// MARK:- WKNavigationDelegate-网页监听
extension NJLiveRoomStreamTool {
// 9页面加载失败时调用
public func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
self.failCallback(webView: webView, error: NJLiveRoomStreamToolError.pageFail)
}
}
该工具类组件地址: GitHub: NJKit
最新的开源GitHub: swiftProject
最彻底的组件化
正在进行中, 逐渐完善文档和项目
可能本仓库长期不更新, 但是组件在更新, 经常pod update下关注
组件仓库:https://github.com/NJHu/NJSpecs.git
GitHub: NJHu | 简书: NJHu | Blog: NJHu | Email: 64hp@163.com
进度
![](https://img.haomeiwen.com/i3753193/e740fbec90606a57.png)
网友评论