美文网首页iOS移动端开发
移动端破解各大直播平台直播流地址(侵立删)

移动端破解各大直播平台直播流地址(侵立删)

作者: NJHu | 来源:发表于2018-07-18 00:08 被阅读48次

思路

  • 各大直播平台支持浏览器播放视频
  • 播放的时候会把直播流地址暴露在标签里边, 绝大部分是在<Video>标签
  • 如果不是<Video>标签怎么获得?

看几张网页截图

移动端获得直播流地址

  • 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

进度

相关文章

网友评论

  • UorDaer:这不是移动端,这是前端。🙄

本文标题:移动端破解各大直播平台直播流地址(侵立删)

本文链接:https://www.haomeiwen.com/subject/imjnpftx.html