android - 如何从 Facebook 视频 URL 中提取可用的视频分辨率?

标签 android facebook video download video-processing

在我的 Facebook Video Downloader Android 应用程序中,我想显示视频分辨率,例如 SDHD 以及大小。目前我正在使用 InputStreamReaderPattern.compile 方法来查找视频的 SDHD URL。 此方法很少为我提供HD视频链接,并且仅提供SD可以下载的URL。

下面是我的链接解析代码

fun linkParsing(url: String, loaded: (item: DownloadItem) -> Unit) {
    val showLogs: Boolean = true
    Log.e("post_url", url)
    return try {
        val getUrl = URL(url)
        val urlConnection =
            getUrl.openConnection() as HttpURLConnection
        var reader: BufferedReader? = null
        urlConnection.setRequestProperty("User-Agent", POST_USER_AGENT)
        urlConnection.setRequestProperty("Accept", "*/*")
        val streamMap = StringBuilder()
        try {
            reader =
                BufferedReader(InputStreamReader(urlConnection.inputStream))
            var line: String?
            while (reader.readLine().also {
                    line = it
                } != null) {
                streamMap.append(line)
            }
        } catch (E: Exception) {
            E.printStackTrace()
            reader?.close()
            urlConnection.disconnect()
        } finally {
            reader?.close()
            urlConnection.disconnect()
        }
        if (streamMap.toString().contains("You must log in to continue.")) {
        } else {
            val metaTAGTitle =
                Pattern.compile("<meta property=\"og:title\"(.+?)\" />")
            val metaTAGTitleMatcher = metaTAGTitle.matcher(streamMap)
            val metaTAGDescription =
                Pattern.compile("<meta property=\"og:description\"(.+?)\" />")
            val metaTAGDescriptionMatcher =
                metaTAGDescription.matcher(streamMap)
            var authorName: String? = ""
            var fileName: String? = ""
            if (metaTAGTitleMatcher.find()) {
                var author =
                    streamMap.substring(metaTAGTitleMatcher.start(), metaTAGTitleMatcher.end())
                Log.e("Extractor", "AUTHOR :: $author")
                author = author.replace("<meta property=\"og:title\" content=\"", "")
                    .replace("\" />", "")
                authorName = author
            } else {
                authorName = "N/A"
            }
            if (metaTAGDescriptionMatcher.find()) {
                var name = streamMap.substring(
                    metaTAGDescriptionMatcher.start(),
                    metaTAGDescriptionMatcher.end()
                )
                Log.e("Extractor", "FILENAME :: $name")
                name = name.replace("<meta property=\"og:description\" content=\"", "")
                    .replace("\" />", "")
                fileName = name
            } else {
                fileName = "N/A"
            }
            val sdVideo =
                Pattern.compile("<meta property=\"og:video\"(.+?)\" />")
            val sdVideoMatcher = sdVideo.matcher(streamMap)
            val imagePattern =
                Pattern.compile("<meta property=\"og:image\"(.+?)\" />")
            val imageMatcher = imagePattern.matcher(streamMap)
            val thumbnailPattern =
                Pattern.compile("<img class=\"_3chq\" src=\"(.+?)\" />")
            val thumbnailMatcher = thumbnailPattern.matcher(streamMap)
            val hdVideo = Pattern.compile("(hd_src):\"(.+?)\"")
            val hdVideoMatcher = hdVideo.matcher(streamMap)
            val facebookFile = DownloadItem()
            facebookFile?.author = authorName
            facebookFile?.filename = fileName
            facebookFile?.postLink = url
            if (sdVideoMatcher.find()) {
                var vUrl = sdVideoMatcher.group()
                vUrl = vUrl.substring(8, vUrl.length - 1) //sd_scr: 8 char
                facebookFile?.sdUrl = vUrl
                facebookFile?.ext = "mp4"
                var imageUrl = streamMap.substring(sdVideoMatcher.start(), sdVideoMatcher.end())
                imageUrl = imageUrl.replace("<meta property=\"og:video\" content=\"", "")
                    .replace("\" />", "").replace("&amp;", "&")
                Log.e("Extractor", "FILENAME :: NULL")
                Log.e("Extractor", "FILENAME :: $imageUrl")
                facebookFile?.sdUrl = URLDecoder.decode(imageUrl, "UTF-8")
                if (showLogs) {
                    Log.e("Extractor", "SD_URL :: Null")
                    Log.e("Extractor", "SD_URL :: $imageUrl")
                }
                if (thumbnailMatcher.find()) {
                    var thumbNailUrl =
                        streamMap.substring(thumbnailMatcher.start(), thumbnailMatcher.end())
                    thumbNailUrl = thumbNailUrl.replace("<img class=\"_3chq\" src=\"", "")
                        .replace("\" />", "").replace("&amp;", "&")
                    Log.e("Extractor", "Thumbnail :: NULL")
                    Log.e("Extractor", "Thumbnail :: $thumbNailUrl")
                    facebookFile?.thumbNailUrl = URLDecoder.decode(thumbNailUrl, "UTF-8")
                }

            }
            if (hdVideoMatcher.find()) {
                var vUrl1 = hdVideoMatcher.group()
                vUrl1 = vUrl1.substring(8, vUrl1.length - 1) //hd_scr: 8 char
                facebookFile?.hdUrl = vUrl1

                if (showLogs) {
                    Log.e("Extractor", "HD_URL :: Null")
                    Log.e("Extractor", "HD_URL :: $vUrl1")
                }

            } else {
                facebookFile?.hdUrl = null
            }
            if (imageMatcher.find()) {
                var imageUrl =
                    streamMap.substring(imageMatcher.start(), imageMatcher.end())
                imageUrl = imageUrl.replace("<meta property=\"og:image\" content=\"", "")
                    .replace("\" />", "").replace("&amp;", "&")
                Log.e("Extractor", "FILENAME :: NULL")
                Log.e("Extractor", "FILENAME :: $imageUrl")
                facebookFile?.imageUrl = URLDecoder.decode(imageUrl, "UTF-8")
            }
            if (facebookFile?.sdUrl == null && facebookFile?.hdUrl == null) {
            }
            loaded(facebookFile!!)
        }
    } catch (e: Exception) {
        e.printStackTrace()
    }
}

我想实现一个功能,可以显示不同的分辨率和尺寸,如图所示。

enter image description here

请注意,我已经使用具有高清 URL 的视频测试了我的 linkParsing 方法,但它只提供了标清 URL。

这是示例视频链接:https://fb.watch/aENyxV7gxs/

如何做到这一点?我找不到任何合适的方法或 GitHub 库。

最佳答案

找到了此问题的解决方案,因此将其作为答案发布。

这可以通过提取网页的页面源,然后解析该 XML 并获取 BASE URL 列表来完成。

步骤如下:

1- 在 Webview 中加载特定视频 URL 并在 onPageFinished 中获取页面源

private fun webViewSetupNotLoggedIn() {
    webView?.settings?.javaScriptEnabled = true
    webView?.settings?.userAgentString = AppConstants.USER_AGENT
    webView?.settings?.useWideViewPort = true
    webView?.settings?.loadWithOverviewMode = true
    webView?.addJavascriptInterface(this, "mJava")
    webView?.post {
        run {
            webView?.loadUrl(“url of your video")
        }
    }
    object : WebViewClient() {
        override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
            if (url == "https://m.facebook.com/login.php" || url.contains("https://m.facebook.com/login.php")
            ) {
                webView?.loadUrl("url of your video")
            }
            return true
        }

        override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
            super.onPageStarted(view, url, favicon)
        }
    }
    webView.webChromeClient = object : WebChromeClient() {
        override fun onProgressChanged(view: WebView?, newProgress: Int) {
            super.onProgressChanged(view, newProgress)
            if (progressBarBottomSheet != null) {
                if (newProgress == 100) {
                    progressBarBottomSheet.visibility = View.GONE
                } else {
                    progressBarBottomSheet.visibility = View.VISIBLE
                }
                progressBarBottomSheet.progress = newProgress
            }
        }
    }
    webView?.webViewClient = object : WebViewClient() {
        override fun onPageFinished(view: WebView?, url: String?) {
            try {
                if (webView?.progress == 100) {
                    var original = webView?.originalUrl
                    var post_link = "url of your video"
                    if (original.equals(post_link)) {
                        var listOfResolutions = arrayListOf<ResolutionDetail>()
                        val progressDialog = activity?.getProgressDialog(false)
                        progressDialog?.show()

                        //Fetch resoultions
                        webView.evaluateJavascript(
                            "(function(){return window.document.body.outerHTML})();"
                        ) { value ->
                            val reader = JsonReader(StringReader(value))
                            reader.isLenient = true
                            try {
                                if (reader.peek() == JsonToken.STRING) {
                                    val domStr = reader.nextString()
                                    domStr?.let {
                                        val xmlString = it
                                        CoroutineScope(Dispatchers.Main).launch {
                                            CoroutineScope(Dispatchers.IO).async {
                                                try {
                                                    getVideoResolutionsFromPageSource((xmlString)) {
                                                        listOfResolutions = it
                                                    }
                                                } catch (e: java.lang.Exception) {
                                                    e.printStackTrace()
                                                    Log.e("Exception", e.message!!)
                                                }
                                            }.await()
                                            progressDialog?.hide()
                                            if (listOfResolutions.size > 0) {
                                                setupResolutionsListDialog(listOfResolutions)
                                            } else {
                                                Toast.makeText(
                                                    context,
                                                    "No Resolutions Found",
                                                    Toast.LENGTH_SHORT
                                                ).show()
                                            }
                                        }
                                    }
                                }
                            } catch (e: IOException) {
                                e.printStackTrace()
                            } finally {
                                reader.close()
                            }
                        }
                    }
                }
            } catch (ex: Exception) {
                ex.printStackTrace()
            }
            super.onPageFinished(view, url)
        }

        @TargetApi(android.os.Build.VERSION_CODES.M)
        override fun onReceivedError(
            view: WebView?,
            request: WebResourceRequest?,
            error: WebResourceError
        ) {

        }

        @SuppressWarnings("deprecation")
        override fun onReceivedError(
            view: WebView?,
            errorCode: Int,
            description: String?,
            failingUrl: String?
        ) {
            super.onReceivedError(view, errorCode, description, failingUrl)
        }

        override fun onLoadResource(view: WebView?, url: String?) {
            Log.e("getData", "onLoadResource")

            super.onLoadResource(view, url)
        }
    }
}

2- 获取页面源时解析以获取视频分辨率 URL

fun getVideoResolutionsFromPageSource(
    pageSourceXmlString: String?,
    finished: (listOfRes: ArrayList<ResolutionDetail>) -> Unit
) {
    //pageSourceXmlString is the Page Source of WebPage of that specific copied video
    //We need to find list of Base URLs from pageSourceXmlString
    //Base URLs are inside an attribute named data-store which is inside a div whose class name starts with  '_53mw;
    //We need to find that div then get data-store which has a JSON as string
    //Parse that JSON and we will get list of adaptationset
    //Each adaptationset has list of representation tags
    // representation is the actual div which contains BASE URLs
    //Note that: BASE URLs have a specific attribute called mimeType
    //mimeType has audio/mp4 and video/mp4 which helps us to figure out whether the url is of an audio or a video
    val listOfResolutions = arrayListOf<ResolutionDetail>()
    if (!pageSourceXmlString?.isEmpty()!!) {
        val document: org.jsoup.nodes.Document = Jsoup.parse(pageSourceXmlString)
        val sampleDiv = document.getElementsByTag("body")
        if (!sampleDiv.isEmpty()) {
            val bodyDocument: org.jsoup.nodes.Document = Jsoup.parse(sampleDiv.html())
            val dataStoreDiv: org.jsoup.nodes.Element? = bodyDocument.select("div._53mw").first()
            val dataStoreAttr = dataStoreDiv?.attr("data-store")
            val jsonObject = JSONObject(dataStoreAttr)
            if (jsonObject.has("dashManifest")) {
                val dashManifestString: String = jsonObject.getString("dashManifest")
                val dashManifestDoc: org.jsoup.nodes.Document = Jsoup.parse(dashManifestString)
                val mdpTagVal = dashManifestDoc.getElementsByTag("MPD")
                val mdpDoc: org.jsoup.nodes.Document = Jsoup.parse(mdpTagVal.html())
                val periodTagVal = mdpDoc.getElementsByTag("Period")
                val periodDocument: org.jsoup.nodes.Document = Jsoup.parse(periodTagVal.html())
                val subBodyDiv: org.jsoup.nodes.Element? = periodDocument.select("body").first()
                subBodyDiv?.children()?.forEach {
                    val adaptionSetDiv: org.jsoup.nodes.Element? =
                        it.select("adaptationset").first()
                    adaptionSetDiv?.children()?.forEach {
                        if (it is org.jsoup.nodes.Element) {
                            val representationDiv: org.jsoup.nodes.Element? =
                                it.select("representation").first()
                            val resolutionDetail = ResolutionDetail()
                            if (representationDiv?.hasAttr("mimetype")!!) {
                                resolutionDetail.mimetype = representationDiv?.attr("mimetype")
                            }
                            if (representationDiv?.hasAttr("width")!!) {
                                resolutionDetail.width =
                                    representationDiv?.attr("width")?.toLong()!!
                            }
                            if (representationDiv?.hasAttr("height")!!) {
                                resolutionDetail.height =
                                    representationDiv.attr("height").toLong()
                            }
                            if (representationDiv?.hasAttr("FBDefaultQuality")!!) {
                                resolutionDetail.FBDefaultQuality =
                                    representationDiv.attr("FBDefaultQuality")
                            }
                            if (representationDiv?.hasAttr("FBQualityClass")!!) {
                                resolutionDetail.FBQualityClass =
                                    representationDiv.attr("FBQualityClass")
                            }
                            if (representationDiv?.hasAttr("FBQualityLabel")!!) {
                                resolutionDetail.FBQualityLabel =
                                    representationDiv.attr("FBQualityLabel")
                            }
                            val representationDoc: org.jsoup.nodes.Document =
                                Jsoup.parse(representationDiv.html())
                            val baseUrlTag = representationDoc.getElementsByTag("BaseURL")
                            if (!baseUrlTag.isEmpty() && !resolutionDetail.FBQualityLabel.equals(
                                    "Source",
                                    ignoreCase = true
                                )
                            ) {
                                resolutionDetail.videoQualityURL = baseUrlTag[0].text()
                                listOfResolutions.add(resolutionDetail)
                            }
                        }
                    }
                }
            }
        }
    }
    finished(listOfResolutions)
}

class ResolutionDetail {
    var width: Long = 0
    var height: Long = 0
    var FBQualityLabel = ""
    var FBDefaultQuality = ""
    var FBQualityClass = ""
    var videoQualityURL = ""
    var mimetype = ""  // [audio/mp4 for audios and video/mp4 for videos]
}

3- 将 videoQualityURL 传递到您的视频下载功能,并且将以所选分辨率下载视频。

关于android - 如何从 Facebook 视频 URL 中提取可用的视频分辨率?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70782618/

相关文章:

java - 单击按钮更改图像

java - GreenDao - 自定义数据缓存无法正常工作

javascript - 如何组合多个注册选项 facebook twitter openid

video - FFMPEG:解释任何编解码器函数指针的参数

video - 使用 ffmpeg 进行 AVI 压缩,同时保持 alpha channel ,用于 Adob​​e Premiere

android - 从适配器调用 fragment 方法

android - 将 SVG 矢量转换为 Android 多密度 png 可绘制对象

javascript - facebook graph api 的 Graph api explorer 中不存在offline_access权限

Facebook 错误 : "An unexpected error has occurred. Please retry your request later."

video - 优化服务器上​​的视频存储/编码 (J2EE)