shuisheng 2 роки тому
батько
коміт
b12d7da341

+ 2 - 0
package.json

@@ -48,12 +48,14 @@
     "socket.io-client": "^4.6.1",
     "unplugin-vue-components": "^0.24.1",
     "vconsole": "^3.15.0",
+    "video.js": "^8.3.0",
     "vue": "^3.2.31",
     "vue-demi": "^0.13.11",
     "vue-router": "^4.0.13",
     "webrtc-adapter": "^8.2.2"
   },
   "devDependencies": {
+    "@types/video.js": "^7.3.52",
     "@babel/core": "^7.14.0",
     "@babel/preset-env": "^7.14.2",
     "@commitlint/cli": "^16.0.1",

+ 193 - 0
pnpm-lock.yaml

@@ -8,6 +8,7 @@ specifiers:
   '@rushstack/eslint-patch': ^1.1.0
   '@soda/friendly-errors-webpack-plugin': ^1.8.1
   '@types/node': ^18.11.9
+  '@types/video.js': ^7.3.52
   '@typescript-eslint/parser': ^5.8.1
   '@vicons/ionicons5': ^0.12.0
   '@vue/compiler-sfc': ^3.2.31
@@ -37,6 +38,7 @@ specifiers:
   eslint-webpack-plugin: ^3.1.1
   file-loader: ^6.2.0
   fork-ts-checker-webpack-plugin: ^7.2.6
+  hls.js: ^1.4.6
   html-webpack-plugin: ^5.5.0
   html-webpack-tags-plugin: ^3.0.1
   husky: ^7.0.0
@@ -66,6 +68,7 @@ specifiers:
   typescript: ^4.6.2
   unplugin-vue-components: ^0.24.1
   vconsole: ^3.15.0
+  video.js: ^8.3.0
   vue: ^3.2.31
   vue-demi: ^0.13.11
   vue-loader: ^17.0.0
@@ -81,6 +84,7 @@ specifiers:
   windicss-webpack-plugin: ^1.7.7
 
 dependencies:
+  '@types/video.js': 7.3.52
   '@vicons/ionicons5': 0.12.0
   axios: 1.3.4
   billd-deploy: 1.0.16_imakxv3mh5kp5z5uouwrjmnj5q
@@ -88,6 +92,7 @@ dependencies:
   billd-scss: 0.0.7
   billd-utils: 0.0.12_typescript@4.9.5
   browser-tool: 1.0.5
+  hls.js: 1.4.6
   js-cookie: 3.0.5
   mediasoup-client: 3.6.84
   msr: 1.3.4
@@ -97,6 +102,7 @@ dependencies:
   socket.io-client: 4.6.1
   unplugin-vue-components: 0.24.1_vue@3.2.47
   vconsole: 3.15.0
+  video.js: 8.3.0
   vue: 3.2.47
   vue-demi: 0.13.11_vue@3.2.47
   vue-router: 4.1.6_vue@3.2.47
@@ -2307,6 +2313,10 @@ packages:
     dependencies:
       '@types/node': 18.15.3
 
+  /@types/video.js/7.3.52:
+    resolution: {integrity: sha512-WFj/HkNVCfkchXDeDU0QbimC356FB5vva3g5mgsjk8n3UMKqP9S522rQAmu9LGPiCmShZRPuAlkXmbp5WId6ow==}
+    dev: false
+
   /@types/ws/8.5.4:
     resolution: {integrity: sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==}
     dependencies:
@@ -2446,6 +2456,48 @@ packages:
     resolution: {integrity: sha512-Iy1EUVRpX0WWxeu1VIReR1zsZLMc4fqpt223czR+Rpnrwu7pt46nbnC2ycO7ItI/uqDLJxnbcMC7FujKs9IfFA==}
     dev: false
 
+  /@videojs/http-streaming/3.0.2_video.js@8.3.0:
+    resolution: {integrity: sha512-iSZkwTLGg3Rx78ypCCq/GsMME89ElNvU02xj7reCE2PlITMQjyYsER1w5AsySvT1A694u5yuSzEzLLGF1cL4pg==}
+    engines: {node: '>=8', npm: '>=5'}
+    peerDependencies:
+      video.js: ^7 || ^8
+    dependencies:
+      '@babel/runtime': 7.21.0
+      '@videojs/vhs-utils': 4.0.0
+      aes-decrypter: 4.0.1
+      global: 4.4.0
+      m3u8-parser: 6.2.0
+      mpd-parser: 1.1.1
+      mux.js: 6.3.0
+      video.js: 8.3.0
+    dev: false
+
+  /@videojs/vhs-utils/3.0.5:
+    resolution: {integrity: sha512-PKVgdo8/GReqdx512F+ombhS+Bzogiofy1LgAj4tN8PfdBx3HSS7V5WfJotKTqtOWGwVfSWsrYN/t09/DSryrw==}
+    engines: {node: '>=8', npm: '>=5'}
+    dependencies:
+      '@babel/runtime': 7.21.0
+      global: 4.4.0
+      url-toolkit: 2.2.5
+    dev: false
+
+  /@videojs/vhs-utils/4.0.0:
+    resolution: {integrity: sha512-xJp7Yd4jMLwje2vHCUmi8MOUU76nxiwII3z4Eg3Ucb+6rrkFVGosrXlMgGnaLjq724j3wzNElRZ71D/CKrTtxg==}
+    engines: {node: '>=8', npm: '>=5'}
+    dependencies:
+      '@babel/runtime': 7.21.0
+      global: 4.4.0
+      url-toolkit: 2.2.5
+    dev: false
+
+  /@videojs/xhr/2.6.0:
+    resolution: {integrity: sha512-7J361GiN1tXpm+gd0xz2QWr3xNWBE+rytvo8J3KuggFaLg+U37gZQ2BuPLcnkfGffy2e+ozY70RHC8jt7zjA6Q==}
+    dependencies:
+      '@babel/runtime': 7.21.0
+      global: 4.4.0
+      is-function: 1.0.2
+    dev: false
+
   /@vue/compiler-core/3.2.47:
     resolution: {integrity: sha512-p4D7FDnQb7+YJmO2iPEv0SQNeNzcbHdGByJDsT4lynf63AFkOTFN07HsiRSvjGo0QrxR/o3d0hUyNCUnBU2Tig==}
     dependencies:
@@ -2711,6 +2763,11 @@ packages:
       - supports-color
     dev: true
 
+  /@xmldom/xmldom/0.8.8:
+    resolution: {integrity: sha512-0LNz4EY8B/8xXY86wMrQ4tz6zEHZv9ehFMJPm8u2gq5lQ71cfRKdaKyxfJAx5aUoyzx0qzgURblTisPGgz3d+Q==}
+    engines: {node: '>=10.0.0'}
+    dev: false
+
   /@xtuc/ieee754/1.2.0:
     resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==}
 
@@ -2765,6 +2822,15 @@ packages:
     engines: {node: '>= 10.0.0'}
     dev: false
 
+  /aes-decrypter/4.0.1:
+    resolution: {integrity: sha512-H1nh/P9VZXUf17AA5NQfJML88CFjVBDuGkp5zDHa7oEhYN9TTpNLJknRY1ie0iSKWlDf6JRnJKaZVDSQdPy6Cg==}
+    dependencies:
+      '@babel/runtime': 7.21.0
+      '@videojs/vhs-utils': 3.0.5
+      global: 4.4.0
+      pkcs7: 1.0.4
+    dev: false
+
   /agent-base/6.0.2:
     resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
     engines: {node: '>= 6.0.0'}
@@ -4448,6 +4514,10 @@ packages:
       entities: 2.2.0
     dev: true
 
+  /dom-walk/0.1.2:
+    resolution: {integrity: sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==}
+    dev: false
+
   /domelementtype/2.3.0:
     resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
     dev: true
@@ -5748,6 +5818,13 @@ packages:
       which: 1.3.1
     dev: true
 
+  /global/4.4.0:
+    resolution: {integrity: sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==}
+    dependencies:
+      min-document: 2.19.0
+      process: 0.11.10
+    dev: false
+
   /globals/11.12.0:
     resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
     engines: {node: '>=4'}
@@ -5885,6 +5962,10 @@ packages:
     engines: {node: '>=12.0.0'}
     dev: false
 
+  /hls.js/1.4.6:
+    resolution: {integrity: sha512-lGv9QfjfjfuGQfLa/28vDFlYWb9Myq5QuvM9qWp5DyElp8jTGMNodTdeAjOLzaA/fN4XHeG+HhTkRGzntwuDZw==}
+    dev: false
+
   /homedir-polyfill/1.0.3:
     resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==}
     engines: {node: '>=0.10.0'}
@@ -6107,6 +6188,10 @@ packages:
     engines: {node: '>=8'}
     dev: true
 
+  /individual/2.0.0:
+    resolution: {integrity: sha512-pWt8hBCqJsUWI/HtcfWod7+N9SgAqyPEaF7JQjwzjn5vGrpg6aQ5qeAFQ7dx//UH4J1O+7xqew+gCeeFt6xN/g==}
+    dev: false
+
   /inflight/1.0.6:
     resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
     dependencies:
@@ -6279,6 +6364,10 @@ packages:
     engines: {node: '>=12'}
     dev: true
 
+  /is-function/1.0.2:
+    resolution: {integrity: sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==}
+    dev: false
+
   /is-glob/4.0.3:
     resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
     engines: {node: '>=0.10.0'}
@@ -6568,6 +6657,10 @@ packages:
     resolution: {integrity: sha512-OYWlK0j+roh+eyaMROlNbS5cd5R25Y+IUpdl7cNdB8HNrkgwQzIS7L9MegxOiWNBj9dQhA/yAxiMwCC5mwNoBw==}
     dev: false
 
+  /keycode/2.2.0:
+    resolution: {integrity: sha512-ps3I9jAdNtRpJrbBvQjpzyFbss/skHqzS+eu4RxKLaEAtFqkjZaB6TZMSivPbLxf4K7VI4SjR0P5mRCX5+Q25A==}
+    dev: false
+
   /kind-of/6.0.3:
     resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
     engines: {node: '>=0.10.0'}
@@ -6819,6 +6912,14 @@ packages:
     dependencies:
       yallist: 4.0.0
 
+  /m3u8-parser/6.2.0:
+    resolution: {integrity: sha512-qlC00JTxYOxawcqg+RB8jbyNwL3foY/nCY61kyWP+RCuJE9APLeqB/nSlTjb4Mg0yRmyERgjswpdQxMvkeoDrg==}
+    dependencies:
+      '@babel/runtime': 7.21.0
+      '@videojs/vhs-utils': 3.0.5
+      global: 4.4.0
+    dev: false
+
   /magic-string/0.25.9:
     resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==}
     dependencies:
@@ -6967,6 +7068,12 @@ packages:
     resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
     engines: {node: '>=6'}
 
+  /min-document/2.19.0:
+    resolution: {integrity: sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==}
+    dependencies:
+      dom-walk: 0.1.2
+    dev: false
+
   /min-indent/1.0.1:
     resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
     engines: {node: '>=4'}
@@ -7028,6 +7135,16 @@ packages:
     engines: {node: '>=0.10.0'}
     dev: true
 
+  /mpd-parser/1.1.1:
+    resolution: {integrity: sha512-uZ/db5wQdlQn1L+OD49YXBhPI9UGeK1SeQE4D5EoaJIhf0WM9X3HDj8d+9PjoG06CgCvGZw3YW/wsHku+CH3yA==}
+    hasBin: true
+    dependencies:
+      '@babel/runtime': 7.21.0
+      '@videojs/vhs-utils': 3.0.5
+      '@xmldom/xmldom': 0.8.8
+      global: 4.4.0
+    dev: false
+
   /mrmime/1.0.1:
     resolution: {integrity: sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==}
     engines: {node: '>=10'}
@@ -7063,6 +7180,15 @@ packages:
   /mute-stream/0.0.8:
     resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==}
 
+  /mux.js/6.3.0:
+    resolution: {integrity: sha512-/QTkbSAP2+w1nxV+qTcumSDN5PA98P0tjrADijIzQHe85oBK3Akhy9AHlH0ne/GombLMz1rLyvVsmrgRxoPDrQ==}
+    engines: {node: '>=8', npm: '>=5'}
+    hasBin: true
+    dependencies:
+      '@babel/runtime': 7.21.0
+      global: 4.4.0
+    dev: false
+
   /mz-modules/2.1.0:
     resolution: {integrity: sha512-sjk8lcRW3vrVYnZ+W+67L/2rL+jbO5K/N6PFGIcLWTiYytNr22Ah9FDXFs+AQntTM1boZcoHi5qS+CV1seuPog==}
     engines: {node: '>=6.0.0'}
@@ -7588,6 +7714,13 @@ packages:
       vue-demi: 0.13.11_vue@3.2.47
     dev: false
 
+  /pkcs7/1.0.4:
+    resolution: {integrity: sha512-afRERtHn54AlwaF2/+LFszyAANTCggGilmcmILUzEjvs3XgFZT+xE6+QWQcAGmu4xajy+Xtj7acLOPdx5/eXWQ==}
+    hasBin: true
+    dependencies:
+      '@babel/runtime': 7.21.0
+    dev: false
+
   /pkg-dir/4.2.0:
     resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==}
     engines: {node: '>=8'}
@@ -8338,6 +8471,11 @@ packages:
   /process-nextick-args/2.0.1:
     resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
 
+  /process/0.11.10:
+    resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
+    engines: {node: '>= 0.6.0'}
+    dev: false
+
   /proxy-addr/2.0.7:
     resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
     engines: {node: '>= 0.10'}
@@ -8786,6 +8924,12 @@ packages:
     dependencies:
       queue-microtask: 1.2.3
 
+  /rust-result/1.0.0:
+    resolution: {integrity: sha512-6cJzSBU+J/RJCF063onnQf0cDUOHs9uZI1oroSGnHOph+CQTIJ5Pp2hK5kEQq1+7yE/EEWfulSNXAQ2jikPthA==}
+    dependencies:
+      individual: 2.0.0
+    dev: false
+
   /rxjs/6.6.7:
     resolution: {integrity: sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==}
     engines: {npm: '>=2.0.0'}
@@ -8804,6 +8948,12 @@ packages:
   /safe-buffer/5.2.1:
     resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
 
+  /safe-json-parse/4.0.0:
+    resolution: {integrity: sha512-RjZPPHugjK0TOzFrLZ8inw44s9bKox99/0AZW9o/BEQVrJfhI+fIHMErnPyRa89/yRXUUr93q+tiN6zhoVV4wQ==}
+    dependencies:
+      rust-result: 1.0.0
+    dev: false
+
   /safe-regex-test/1.0.0:
     resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==}
     dependencies:
@@ -9865,6 +10015,10 @@ packages:
     dependencies:
       punycode: 2.3.0
 
+  /url-toolkit/2.2.5:
+    resolution: {integrity: sha512-mtN6xk+Nac+oyJ/PrI7tzfmomRVNFIWKUbG8jdYFt52hxbiReFAXIjYskvu64/dvuW71IcB7lV8l0HvZMac6Jg==}
+    dev: false
+
   /urllib/2.40.0:
     resolution: {integrity: sha512-XDZjoijtzsbkXTXgM+A/sJM002nwoYsc46YOYr6MNH2jUUw1nCBf2ywT1WaPsVEWJX4Yr+9isGmYj4+yofFn9g==}
     engines: {node: '>= 0.10.0'}
@@ -9954,6 +10108,45 @@ packages:
       vue: 3.2.47
     dev: false
 
+  /video.js/8.3.0:
+    resolution: {integrity: sha512-Vp3mqMLSUE354t+G8CbZKwcV520VKoS5fow8zjnEEKFuqStmkmnvK7/FurP6zuP/oWGJ1rqlKxML56kmJOrwRw==}
+    dependencies:
+      '@babel/runtime': 7.21.0
+      '@videojs/http-streaming': 3.0.2_video.js@8.3.0
+      '@videojs/vhs-utils': 4.0.0
+      '@videojs/xhr': 2.6.0
+      aes-decrypter: 4.0.1
+      global: 4.4.0
+      keycode: 2.2.0
+      m3u8-parser: 6.2.0
+      mpd-parser: 1.1.1
+      mux.js: 6.3.0
+      safe-json-parse: 4.0.0
+      videojs-contrib-quality-levels: 3.0.0_video.js@8.3.0
+      videojs-font: 4.1.0
+      videojs-vtt.js: 0.15.4
+    dev: false
+
+  /videojs-contrib-quality-levels/3.0.0_video.js@8.3.0:
+    resolution: {integrity: sha512-sNx38EYUx+Q+gmup1gVTv9P9/sPs28rM7gZOx1sedaHoKxEdYB+ysOGfHj6MSELBMNGMj6ZspdrpSiWguGvGxA==}
+    engines: {node: '>=14', npm: '>=6'}
+    peerDependencies:
+      video.js: ^6 || ^7 || ^8
+    dependencies:
+      global: 4.4.0
+      video.js: 8.3.0
+    dev: false
+
+  /videojs-font/4.1.0:
+    resolution: {integrity: sha512-X1LuPfLZPisPLrANIAKCknZbZu5obVM/ylfd1CN+SsCmPZQ3UMDPcvLTpPBJxcBuTpHQq2MO1QCFt7p8spnZ/w==}
+    dev: false
+
+  /videojs-vtt.js/0.15.4:
+    resolution: {integrity: sha512-r6IhM325fcLb1D6pgsMkTQT1PpFdUdYZa1iqk7wJEu+QlibBwATPfPc9Bg8Jiym0GE5yP1AG2rMLu+QMVWkYtA==}
+    dependencies:
+      global: 4.4.0
+    dev: false
+
   /vite/4.2.0_34kcdhufoak4xbjfji44lxubke:
     resolution: {integrity: sha512-AbDTyzzwuKoRtMIRLGNxhLRuv1FpRgdIw+1y6AQG73Q5+vtecmvzKo/yk8X/vrHDpETRTx01ABijqUHIzBXi0g==}
     engines: {node: ^14.18.0 || >=16.0.0}

+ 1 - 0
src/components/VideoControls/index.vue

@@ -45,6 +45,7 @@ const appStore = useAppStore();
   box-sizing: border-box;
   padding-left: 10px;
   width: 100%;
+  z-index: 1;
   height: 40px;
   background-image: linear-gradient(
     -180deg,

+ 75 - 4
src/hooks/use-play.ts

@@ -1,5 +1,10 @@
-import { onMounted, onUnmounted, ref } from 'vue';
+import 'video.js/dist/video-js.min.css';
 
+import videoJs from 'video.js';
+import Player from 'video.js/dist/types/player';
+import { onMounted, onUnmounted, ref, watch } from 'vue';
+
+import { useAppStore } from '@/store/app';
 // @ts-ignore
 const flvJs = window.flvjs;
 
@@ -14,13 +19,13 @@ export function useFlvPlay() {
     }
   });
 
-  function destroy() {
+  function destroyFlv() {
     if (flvPlayer.value) {
       flvPlayer.value.destroy();
     }
   }
 
-  async function startPlay(data: {
+  async function startFlvPlay(data: {
     flvurl: string;
     videoEl: HTMLVideoElement;
   }) {
@@ -45,5 +50,71 @@ export function useFlvPlay() {
     }
   }
 
-  return { startPlay, destroy };
+  return { startFlvPlay, destroyFlv };
+}
+
+export function useHlsPlay() {
+  const hlsPlayer = ref<Player>();
+  const videoEl = ref<HTMLVideoElement>();
+  const appStore = useAppStore();
+
+  onMounted(() => {});
+
+  onUnmounted(() => {
+    if (hlsPlayer.value) {
+      hlsPlayer.value.dispose();
+    }
+  });
+
+  function destroyHls() {
+    if (hlsPlayer.value) {
+      hlsPlayer.value.dispose();
+    }
+  }
+
+  watch(
+    () => appStore.muted,
+    (newVal) => {
+      if (videoEl.value) {
+        videoEl.value.muted = newVal;
+      }
+    }
+  );
+
+  function startHlsPlay(data: { hlsurl: string; videoEl: HTMLVideoElement }) {
+    console.log('startHlsPlay', data.hlsurl);
+    if (hlsPlayer.value) {
+      hlsPlayer.value.dispose();
+    }
+    const appStore = useAppStore();
+    const newVideo = document.createElement('video');
+    newVideo.muted = appStore.muted;
+    videoEl.value = newVideo;
+    data.videoEl.parentElement?.appendChild(newVideo);
+    return new Promise((resolve, reject) => {
+      hlsPlayer.value = videoJs(
+        newVideo,
+        {
+          sources: [
+            {
+              src: data.hlsurl,
+              type: 'application/x-mpegURL',
+            },
+          ],
+        },
+        function onPlayerReady() {
+          console.log('Your player is ready!');
+          // @ts-ignore
+          this.play();
+          resolve('ok');
+          // @ts-ignore
+          this.on('ended', function () {
+            console.log('Awww...over so soon?!');
+          });
+        }
+      );
+    });
+  }
+
+  return { startHlsPlay, destroyHls };
 }

+ 25 - 8
src/hooks/use-pull.ts

@@ -1,9 +1,9 @@
-import { getRandomString } from 'billd-utils';
+import { getRandomString, judgeDevice } from 'billd-utils';
 import { Ref, nextTick, onUnmounted, reactive, ref, watch } from 'vue';
 import { useRoute } from 'vue-router';
 
 import { fetchRtcV1Play } from '@/api/srs';
-import { useFlvPlay } from '@/hooks/use-play';
+import { useFlvPlay, useHlsPlay } from '@/hooks/use-play';
 import {
   DanmuMsgTypeEnum,
   IAnswer,
@@ -54,6 +54,7 @@ export function usePull({
   const currentLiveRoom = ref<ILive>();
   const streamurl = ref('');
   const flvurl = ref('');
+  const hlsurl = ref('');
   const coverImg = ref('');
   const danmuStr = ref('');
   const balance = ref('0.00');
@@ -69,7 +70,8 @@ export function usePull({
     }[]
   >([]);
 
-  const { startPlay } = useFlvPlay();
+  const { startFlvPlay } = useFlvPlay();
+  const { startHlsPlay } = useHlsPlay();
 
   const track = reactive({
     audio: 1,
@@ -457,6 +459,7 @@ export function usePull({
         track.video = data.data.track_video!;
         coverImg.value = data.data.live_room?.cover_img!;
         flvurl.value = data.data.live_room?.flv_url!;
+        hlsurl.value = data.data.live_room?.hls_url!;
         streamurl.value = data.data.live_room!.rtmp_url!.replace(
           'rtmp',
           'webrtc'
@@ -465,18 +468,32 @@ export function usePull({
         if (route.query.liveType === liveTypeEnum.srsWebrtcPull) {
           instance.send({ msgType: WsMsgTypeEnum.getLiveUser });
         } else if (route.query.liveType === liveTypeEnum.srsFlvPull) {
-          await startPlay({
+          await startFlvPlay({
             flvurl: flvurl.value,
             videoEl: remoteVideoRef.value!,
           });
+        } else if (route.query.liveType === liveTypeEnum.srsHlsPull) {
+          await startHlsPlay({
+            hlsurl: hlsurl.value,
+            videoEl: remoteVideoRef.value!,
+          });
+          videoLoading.value = false;
         } else if (
           data.data.live_room?.type === LiveRoomTypeEnum.user_obs ||
           data.data.live_room?.type === LiveRoomTypeEnum.system
         ) {
-          await startPlay({
-            flvurl: flvurl.value,
-            videoEl: remoteVideoRef.value!,
-          });
+          if (judgeDevice().isIphone) {
+            await startHlsPlay({
+              hlsurl: flvurl.value,
+              videoEl: remoteVideoRef.value!,
+            });
+            videoLoading.value = false;
+          } else {
+            await startFlvPlay({
+              flvurl: flvurl.value,
+              videoEl: remoteVideoRef.value!,
+            });
+          }
         }
       }
     );

+ 1 - 0
src/interface.ts

@@ -184,6 +184,7 @@ export enum liveTypeEnum {
   webrtcPull = 'webrtcPull',
   srsWebrtcPull = 'srsWebrtcPull',
   srsFlvPull = 'srsFlvPull',
+  srsHlsPull = 'srsHlsPull',
   srsPush = 'srsPush',
   webrtcPush = 'webrtcPush',
 }

+ 54 - 12
src/views/home/index.vue

@@ -68,6 +68,17 @@
             >
               进入直播(flv)
             </div>
+            <div
+              v-if="
+                currentLiveRoom.live_room?.type === LiveRoomTypeEnum.system ||
+                currentLiveRoom.live_room?.type === LiveRoomTypeEnum.user_obs ||
+                currentLiveRoom?.live_room?.type === LiveRoomTypeEnum.user_srs
+              "
+              class="btn hls"
+              @click="joinHlsRoom()"
+            >
+              进入直播(hls)
+            </div>
           </div>
         </template>
       </div>
@@ -123,7 +134,7 @@ import { nextTick, onMounted, ref } from 'vue';
 import { useRouter } from 'vue-router';
 
 import { fetchLiveList } from '@/api/live';
-import { useFlvPlay } from '@/hooks/use-play';
+import { useFlvPlay, useHlsPlay } from '@/hooks/use-play';
 import { ILive, LiveRoomTypeEnum, liveTypeEnum } from '@/interface';
 import { routerName } from '@/router';
 import { useAppStore } from '@/store/app';
@@ -135,23 +146,27 @@ const liveRoomList = ref<ILive[]>([]);
 const currentLiveRoom = ref<ILive>();
 const localVideoRef = ref<HTMLVideoElement>();
 
-const { startPlay, destroy } = useFlvPlay();
+const { startFlvPlay, destroyFlv } = useFlvPlay();
+const { startHlsPlay } = useHlsPlay();
 
 function changeLiveRoom(item: ILive) {
   currentLiveRoom.value = item;
-  console.log(item, 22222);
   nextTick(async () => {
     if (
       item.live_room?.type === LiveRoomTypeEnum.user_srs ||
       item.live_room?.type === LiveRoomTypeEnum.user_obs ||
       item.live_room?.type === LiveRoomTypeEnum.system
     ) {
-      await startPlay({
-        flvurl: item.live_room.flv_url!,
+      // await startFlvPlay({
+      //   flvurl: item.live_room.flv_url!,
+      //   videoEl: localVideoRef.value!,
+      // });
+      await startHlsPlay({
+        hlsurl: item.live_room.hls_url!,
         videoEl: localVideoRef.value!,
       });
     } else {
-      destroy();
+      destroyFlv();
     }
   });
 }
@@ -174,12 +189,16 @@ async function getLiveRoomList() {
               LiveRoomTypeEnum.user_obs ||
             currentLiveRoom.value?.live_room?.type === LiveRoomTypeEnum.system
           ) {
-            await startPlay({
-              flvurl: currentLiveRoom.value.live_room.flv_url!,
+            // await startFlvPlay({
+            //   flvurl: currentLiveRoom.value.live_room.flv_url!,
+            //   videoEl: localVideoRef.value!,
+            // });
+            await startHlsPlay({
+              hlsurl: currentLiveRoom.value.live_room.hls_url!,
               videoEl: localVideoRef.value!,
             });
           } else {
-            destroy();
+            destroyFlv();
           }
         });
       }
@@ -228,6 +247,17 @@ function joinFlvRoom() {
     },
   });
 }
+function joinHlsRoom() {
+  // console.log(currentLiveRoom.value?.live_room_id);
+  // return;
+  router.push({
+    name: routerName.pull,
+    params: { roomId: currentLiveRoom.value?.live_room_id },
+    query: {
+      liveType: liveTypeEnum.srsHlsPull,
+    },
+  });
+}
 </script>
 
 <style lang="scss" scoped>
@@ -258,12 +288,22 @@ function joinFlvRoom() {
 
         inset: 0;
       }
-
-      #localVideo {
-        position: relative;
+      :deep(video) {
+        position: absolute;
+        top: 0;
+        left: 50%;
         width: 100%;
         height: 100%;
+        transform: translate(-50%);
       }
+      // #localVideo {
+      //   position: relative;
+      //   width: 100%;
+      //   height: 100%;
+      //   video {
+      //     height: 100%;
+      //   }
+      // }
       .controls {
         display: none;
       }
@@ -280,6 +320,7 @@ function joinFlvRoom() {
         position: absolute;
         top: 50%;
         left: 50%;
+        z-index: 1;
         display: none;
         align-items: center;
         transform: translate(-50%, -50%);
@@ -300,6 +341,7 @@ function joinFlvRoom() {
             margin-right: 10px;
           }
           &.flv {
+            margin-right: 10px;
           }
         }
       }

+ 10 - 2
src/views/pull/index.vue

@@ -427,11 +427,19 @@ onMounted(() => {
 
           inset: 0;
         }
-        #remoteVideo {
-          position: relative;
+        :deep(video) {
+          position: absolute;
+          top: 0;
+          left: 50%;
           width: 100%;
           height: 100%;
+          transform: translate(-50%);
         }
+        // #remoteVideo {
+        //   position: relative;
+        //   width: 100%;
+        //   height: 100%;
+        // }
         .controls {
           display: none;
         }