m3u8 视频解析工具

在线版本
http://blog.luckly-mjw.cn/tool-show/m3u8-downloader/index.html

研发背景
m3u8视频格式简介

m3u8视频格式原理:将完整的视频拆分成多个 .ts 视频碎片,.m3u8 文件详细记录每个视频片段的地址。
视频播放时,会先读取 .m3u8 文件,再逐个下载播放 .ts 视频片段。
常用于直播业务,也常用该方法规避视频窃取的风险。加大视频窃取难度。
鉴于 m3u8 以上特点,无法简单通过视频链接下载,需使用特定下载软件。

但软件下载过程繁琐,试错成本高。
使用软件的下载情况不稳定,常出现浏览器正常播放,但软件下载速度慢,甚至无法正常下载的情况。
软件被编译打包,无法了解内部运行机制,不清楚里面到底发生了什么。
基于以上原因,开发了本工具。

油猴脚本

// ==UserScript==
// @name         m3u8-downloader-test
// @namespace    https://github.com/Momo707577045/m3u8-downloader
// @version      0.10.1
// @description  https://github.com/Momo707577045/m3u8-downloader 配套插件
// @author       Momo707577045
// @include      *
// @exclude      http://blog.luckly-mjw.cn/tool-show/m3u8-downloader/index.html
// @exclude      https://blog.luckly-mjw.cn/tool-show/m3u8-downloader/index.html
// @exclude      https://www.bilibili.com/*
// @downloadURL	 https://blog.luckly-mjw.cn/tool-show/m3u8-downloader/m3u8-downloader.user.js
// @updateURL	   https://blog.luckly-mjw.cn/tool-show/m3u8-downloader/m3u8-downloader.user.js
// @grant        none
// @run-at document-start
// ==/UserScript==

(function () {
  'use strict';
  var showMp4 = true
  var m3u8Target = ''
  var mp4Objs = []
  var originXHR = window.XMLHttpRequest
  var windowOpen = window.open

  function ajax(options) {
    options = options || {};
    let xhr = new originXHR();
    if (options.type === 'file') {
      xhr.responseType = 'arraybuffer';
    }

    xhr.onreadystatechange = function () {
      if (xhr.readyState === 4) {
        let status = xhr.status;
        if (status >= 200 && status < 300) {
          options.success && options.success(xhr.response);
        } else {
          options.fail && options.fail(status);
        }
      }
    };

    xhr.open("GET", options.url, true);
    xhr.send(null);
  }

  // 普通下载
  function downloadWithA(url, name) {
    const a = document.createElement('a')
    a.href = url
    a.download = name
    a.style.display = 'none'
    document.body.appendChild(a)
    a.click()
    a.remove()
  }

  // 检测 m3u8 链接的有效性
  function checkM3u8Url(url) {
    ajax({
      url,
      success: (fileStr) => {
        if (/(png|image|ts|jpg|mp4|jpeg|EXTINF)/.test(fileStr)) {
          appendDom()
          document.getElementById('m3u8-jump').style.display = 'block'
          document.getElementById('m3u8-close').style.display = 'block'
          document.getElementById('m3u8-append').style.display = 'block'

          const urlObj = new URL(url)
          urlObj.searchParams.append('title', getTitle())
          m3u8Target = urlObj.href
          console.log('【m3u8】----------------------------------------')
          console.log(urlObj)
          console.log('http://blog.luckly-mjw.cn/tool-show/m3u8-downloader/index.html?source=' + m3u8Target)
        }
      }
    })
  }

  // 定时器,检查 mp4 视频资源
  function checkVideo() {
    let $videoList = document.getElementsByTagName('video')
    for (let i = 0, length = $videoList.length; i < length; i++) {
      const url = $videoList[i].currentSrc
      if (url.indexOf('.mp4') > 0 && !mp4Objs.find(mp4 => mp4.url === url)) {
        appendDom();
        document.getElementById('mp4-show').style.display = 'block'
        mp4Objs.push({
          url,
          fileName: url.slice(url.lastIndexOf('/') + 1).split('?')[0],
        });
      }
    }
    setTimeout(checkVideo, 3000);
  }

  function resetAjax() {
    if (window._hadResetAjax) { // 如果已经重置过,则不再进入。解决开发时局部刷新导致重新加载问题
      return
    }
    window._hadResetAjax = true

    var originOpen = originXHR.prototype.open
    window.XMLHttpRequest = function () {
      var realXHR = new originXHR()
      realXHR.open = function (method, url) {
        url.toString() && url.toString().indexOf('.m3u8') > 0 && checkM3u8Url(url.toString())
        // if (url.toString() && url.toString().toLocaleLowerCase().indexOf('.mp4') > 0) {
        //   appendDom();
        //   document.getElementById('mp4-show').style.display = 'block'
        //   mp4Objs.push({
        //     url,
        //     fileName: url.slice(url.lastIndexOf('/') + 1).split('?')[0],
        //   });
        // }
        originOpen.call(realXHR, method, url)
      }
      return realXHR
    }
    XMLHttpRequest.UNSENT = 0;
    XMLHttpRequest.OPENED = 1;
    XMLHttpRequest.HEADERS_RECEIVED = 2;
    XMLHttpRequest.LOADING = 3;
    XMLHttpRequest.DONE = 4;
  }

  // 获取顶部 window title,因可能存在跨域问题,故使用 try catch 进行保护
  function getTitle() {
    let title = document.title;
    try {
      title = window.top.document.title
    } catch (error) {
      console.log(error)
    }
    return title
  }

  function appendDom() {
    if (document.getElementById('m3u8-download-dom')) {
      return
    }
    var domStr = `
    
MP4下载
跳转下载
注入下载
`
var $section = document.createElement('section') $section.id = 'm3u8-download-dom' $section.style.position = 'fixed' $section.style.zIndex = '9999' $section.style.bottom = '20px' $section.style.right = '20px' $section.style.textAlign = 'center' $section.innerHTML = domStr document.body.appendChild($section); var mp4Show = document.getElementById('mp4-show') var m3u8Jump = document.getElementById('m3u8-jump') var m3u8Close = document.getElementById('m3u8-close') var m3u8Append = document.getElementById('m3u8-append') mp4Show.addEventListener('click', function () { showMp4 = !showMp4 mp4Show.innerHTML = showMp4 ? 'MP4下载' : '关闭MP4' switchMp4Download(); }) m3u8Close.addEventListener('click', function () { $section.remove() }) m3u8Jump.addEventListener('click', function () { windowOpen('//blog.luckly-mjw.cn/tool-show/m3u8-downloader/index.html?source=' + m3u8Target) }) m3u8Append.addEventListener('click', function () { var _hmt = _hmt || []; (function () { var hm = document.createElement("script"); hm.src = "https://hm.baidu.com/hm.js?1f12b0865d866ae1b93514870d93ce89"; var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(hm, s); })(); ajax({ url: 'https://blog.luckly-mjw.cn/tool-show/m3u8-downloader/index.html', success: (fileStr) => { let fileList = fileStr.split(`

你可能感兴趣的:(笔记,音视频)