jessibuca.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  1. <template>
  2. <div
  3. ref="container"
  4. class="jessibuca-container"
  5. :class="{'jessibuca-fullscreen': fullscreen}"
  6. @dblclick="fullscreenSwich"
  7. >
  8. <div class="jessibuca-player-wrapper"></div>
  9. <div id="buttonsBox" class="buttons-box">
  10. <div class="buttons-box-left">
  11. <i v-if="!playing" class="iconfont icon-play jessibuca-btn" @click="playBtnClick" />
  12. <i v-if="playing" class="iconfont icon-pause jessibuca-btn" @click="pause" />
  13. <i class="iconfont icon-stop jessibuca-btn" @click="destroy" />
  14. <i v-if="isNotMute" class="iconfont icon-audio-high jessibuca-btn" @click="mute()" />
  15. <i v-if="!isNotMute" class="iconfont icon-audio-mute jessibuca-btn" @click="cancelMute()" />
  16. </div>
  17. <div class="buttons-box-right">
  18. <span class="jessibuca-btn">{{ kBps }} kb/s</span>
  19. <!-- <i class="iconfont icon-file-record1 jessibuca-btn"></i>-->
  20. <!-- <i class="iconfont icon-xiangqing2 jessibuca-btn" ></i>-->
  21. <i
  22. class="iconfont icon-camera1196054easyiconnet jessibuca-btn"
  23. style="font-size: 1rem !important"
  24. @click="screenshot"
  25. />
  26. <i class="iconfont icon-shuaxin11 jessibuca-btn" @click="playBtnClick" />
  27. <i v-if="!fullscreen" class="iconfont icon-weibiaoti10 jessibuca-btn" @click="fullscreenSwich" />
  28. <i v-if="fullscreen" class="iconfont icon-weibiaoti11 jessibuca-btn" @click="fullscreenSwich" />
  29. </div>
  30. </div>
  31. </div>
  32. </template>
  33. <script>
  34. const jessibucaPlayer = {}
  35. export default {
  36. name: 'Jessibuca',
  37. props: ['videoUrl', 'error', 'hasAudio', 'height'],
  38. data() {
  39. return {
  40. playing: false,
  41. isNotMute: false,
  42. quieting: false,
  43. fullscreen: false,
  44. loaded: false, // mute
  45. speed: 0,
  46. performance: '', // 工作情况
  47. kBps: 0,
  48. btnDom: null,
  49. videoInfo: null,
  50. volume: 1,
  51. rotate: 0,
  52. vod: true, // 点播
  53. forceNoOffscreen: false
  54. }
  55. },
  56. watch: {
  57. videoUrl: {
  58. handler(val, _) {
  59. this.$nextTick(() => {
  60. this.play(val)
  61. })
  62. },
  63. immediate: true
  64. }
  65. },
  66. created() {
  67. const paramUrl = decodeURIComponent(this.$route.params.url)
  68. this.$nextTick(() => {
  69. this.updatePlayerDomSize()
  70. window.onresize = this.updatePlayerDomSize
  71. if (typeof (this.videoUrl) === 'undefined') {
  72. this.videoUrl = paramUrl
  73. }
  74. this.btnDom = document.getElementById('buttonsBox')
  75. })
  76. },
  77. // mounted() {
  78. // const ro = new ResizeObserver(entries => {
  79. // entries.forEach(entry => {
  80. // this.updatePlayerDomSize()
  81. // });
  82. // });
  83. // ro.observe(this.$refs.container);
  84. // },
  85. mounted() {
  86. this.updatePlayerDomSize()
  87. },
  88. destroyed() {
  89. if (jessibucaPlayer[this._uid]) {
  90. jessibucaPlayer[this._uid].destroy()
  91. }
  92. this.playing = false
  93. this.loaded = false
  94. this.performance = ''
  95. },
  96. methods: {
  97. updatePlayerDomSize() {
  98. const dom = this.$refs.container
  99. if (!dom) return;
  100. if (!this.parentNodeResizeObserver) {
  101. this.parentNodeResizeObserver = new ResizeObserver(entries => {
  102. this.updatePlayerDomSize()
  103. })
  104. this.parentNodeResizeObserver.observe(dom.parentNode)
  105. }
  106. // 获取父容器尺寸
  107. const boxWidth = dom.parentNode.clientWidth
  108. const boxHeight = dom.parentNode.clientHeight
  109. // 检查是否处于全屏状态
  110. const isFullscreen = this.isFullscreen();
  111. let width, height;
  112. if (isFullscreen) {
  113. // 全屏模式,使用窗口尺寸
  114. width = window.innerWidth;
  115. height = window.innerHeight;
  116. } else {
  117. // 非全屏模式,使用16:9比例
  118. width = boxWidth
  119. height = (9 / 16) * width
  120. // 如果计算出的高度超过容器高度,则以容器高度为基准重新计算宽度
  121. if (boxHeight > 0 && height > boxHeight) {
  122. height = boxHeight
  123. width = height * 16 / 9
  124. }
  125. }
  126. // 限制尺寸不超过视口
  127. const clientHeight = Math.min(document.body.clientHeight, document.documentElement.clientHeight)
  128. if (!isFullscreen && height > clientHeight) {
  129. height = clientHeight
  130. width = (16 / 9) * height
  131. }
  132. this.playerWidth = width
  133. this.playerHeight = height
  134. // 应用尺寸到容器
  135. dom.style.width = `${width}px`
  136. dom.style.height = `${height}px`
  137. // 如果播放器存在,更新播放器尺寸
  138. if (this.playing && jessibucaPlayer[this._uid]) {
  139. jessibucaPlayer[this._uid].resize(width, height)
  140. }
  141. },
  142. // 公共方法:调整大小
  143. resize() {
  144. this.updatePlayerDomSize()
  145. },
  146. // 判断是否处于全屏状态
  147. isFullscreen() {
  148. return document.fullscreenElement ||
  149. document.msFullscreenElement ||
  150. document.mozFullScreenElement ||
  151. document.webkitFullscreenElement || false
  152. },
  153. create() {
  154. const options = {
  155. container: this.$refs.container,
  156. autoWasm: true,
  157. background: '',
  158. controlAutoHide: false,
  159. debug: false,
  160. decoder: 'static/js/jessibuca/decoder.js',
  161. forceNoOffscreen: false,
  162. hasAudio: typeof (this.hasAudio) === 'undefined' ? true : this.hasAudio,
  163. heartTimeout: 5,
  164. heartTimeoutReplay: true,
  165. heartTimeoutReplayTimes: 3,
  166. hiddenAutoPause: false,
  167. hotKey: true,
  168. isFlv: false,
  169. isFullResize: false,
  170. isNotMute: this.isNotMute,
  171. isResize: true,
  172. keepScreenOn: true,
  173. loadingText: '请稍等, 视频加载中......',
  174. loadingTimeout: 10,
  175. loadingTimeoutReplay: true,
  176. loadingTimeoutReplayTimes: 3,
  177. openWebglAlignment: false,
  178. operateBtns: {
  179. fullscreen: false,
  180. screenshot: false,
  181. play: false,
  182. audio: false,
  183. record: false
  184. },
  185. recordType: 'mp4',
  186. rotate: 0,
  187. showBandwidth: false,
  188. supportDblclickFullscreen: false,
  189. timeout: 10,
  190. useMSE: true,
  191. useWCS: false,
  192. useWebFullScreen: true,
  193. videoBuffer: 0.1,
  194. wasmDecodeErrorReplay: true,
  195. wcsUseVideoRender: true
  196. }
  197. console.log('Jessibuca -> options: ', options)
  198. jessibucaPlayer[this._uid] = new window.Jessibuca({ ...options })
  199. const jessibuca = jessibucaPlayer[this._uid]
  200. const _this = this
  201. jessibuca.on('pause', function() {
  202. _this.playing = false
  203. })
  204. jessibuca.on('play', function() {
  205. _this.playing = true
  206. })
  207. jessibuca.on('fullscreen', function(msg) {
  208. _this.fullscreen = msg
  209. })
  210. jessibuca.on('mute', function(msg) {
  211. _this.isNotMute = !msg
  212. })
  213. jessibuca.on('performance', function(performance) {
  214. let show = '卡顿'
  215. if (performance === 2) {
  216. show = '非常流畅'
  217. } else if (performance === 1) {
  218. show = '流畅'
  219. }
  220. _this.performance = show
  221. })
  222. jessibuca.on('kBps', function(kBps) {
  223. _this.kBps = Math.round(kBps)
  224. })
  225. jessibuca.on('videoInfo', function(msg) {
  226. console.log('Jessibuca -> videoInfo: ', msg)
  227. })
  228. jessibuca.on('audioInfo', function(msg) {
  229. console.log('Jessibuca -> audioInfo: ', msg)
  230. })
  231. jessibuca.on('error', function(msg) {
  232. console.log('Jessibuca -> error: ', msg)
  233. })
  234. jessibuca.on('timeout', function(msg) {
  235. console.log('Jessibuca -> timeout: ', msg)
  236. })
  237. jessibuca.on('loadingTimeout', function(msg) {
  238. console.log('Jessibuca -> timeout: ', msg)
  239. })
  240. jessibuca.on('delayTimeout', function(msg) {
  241. console.log('Jessibuca -> timeout: ', msg)
  242. })
  243. jessibuca.on('playToRenderTimes', function(msg) {
  244. console.log('Jessibuca -> playToRenderTimes: ', msg)
  245. })
  246. },
  247. playBtnClick: function(event) {
  248. this.play(this.videoUrl)
  249. },
  250. play: function(url) {
  251. console.log('Jessibuca -> url: ', url)
  252. if (jessibucaPlayer[this._uid]) {
  253. this.destroy()
  254. }
  255. this.create()
  256. jessibucaPlayer[this._uid].on('play', () => {
  257. this.playing = true
  258. this.loaded = true
  259. this.quieting = jessibuca.quieting
  260. })
  261. if (jessibucaPlayer[this._uid].hasLoaded()) {
  262. jessibucaPlayer[this._uid].play(url)
  263. } else {
  264. jessibucaPlayer[this._uid].on('load', () => {
  265. jessibucaPlayer[this._uid].play(url)
  266. })
  267. }
  268. },
  269. pause: function() {
  270. if (jessibucaPlayer[this._uid]) {
  271. jessibucaPlayer[this._uid].pause()
  272. }
  273. this.playing = false
  274. this.err = ''
  275. this.performance = ''
  276. },
  277. screenshot: function() {
  278. if (jessibucaPlayer[this._uid]) {
  279. jessibucaPlayer[this._uid].screenshot()
  280. }
  281. },
  282. mute: function() {
  283. if (jessibucaPlayer[this._uid]) {
  284. jessibucaPlayer[this._uid].mute()
  285. }
  286. },
  287. cancelMute: function() {
  288. if (jessibucaPlayer[this._uid]) {
  289. jessibucaPlayer[this._uid].cancelMute()
  290. }
  291. },
  292. destroy: function() {
  293. if (jessibucaPlayer[this._uid]) {
  294. jessibucaPlayer[this._uid].destroy()
  295. }
  296. if (document.getElementById('buttonsBox') == null) {
  297. this.$refs.container.appendChild(this.btnDom)
  298. }
  299. jessibucaPlayer[this._uid] = null
  300. this.playing = false
  301. this.err = ''
  302. this.performance = ''
  303. },
  304. fullscreenSwich: function() {
  305. const isFull = this.isFullscreen()
  306. if (!isFull) {
  307. // 进入全屏
  308. try {
  309. const container = this.$refs.container;
  310. // 尝试使用HTML5全屏API
  311. if (container.requestFullscreen) {
  312. container.requestFullscreen();
  313. } else if (container.webkitRequestFullscreen) {
  314. container.webkitRequestFullscreen();
  315. } else if (container.msRequestFullscreen) {
  316. container.msRequestFullscreen();
  317. } else if (container.mozRequestFullScreen) {
  318. container.mozRequestFullScreen();
  319. } else {
  320. // 如果原生API不可用,使用Jessibuca的全屏API
  321. if (jessibucaPlayer[this._uid]) {
  322. jessibucaPlayer[this._uid].setFullscreen(true);
  323. }
  324. }
  325. // 设置全屏标志
  326. this.fullscreen = true;
  327. } catch (e) {
  328. console.error('全屏切换失败:', e);
  329. }
  330. } else {
  331. // 退出全屏
  332. try {
  333. if (document.exitFullscreen) {
  334. document.exitFullscreen();
  335. } else if (document.webkitExitFullscreen) {
  336. document.webkitExitFullscreen();
  337. } else if (document.msExitFullscreen) {
  338. document.msExitFullscreen();
  339. } else if (document.mozCancelFullScreen) {
  340. document.mozCancelFullScreen();
  341. } else {
  342. // 如果原生API不可用,使用Jessibuca的全屏API
  343. if (jessibucaPlayer[this._uid]) {
  344. jessibucaPlayer[this._uid].setFullscreen(false);
  345. }
  346. }
  347. // 设置全屏标志
  348. this.fullscreen = false;
  349. } catch (e) {
  350. console.error('退出全屏失败:', e);
  351. }
  352. }
  353. // 重新计算尺寸
  354. setTimeout(() => {
  355. this.updatePlayerDomSize();
  356. }, 300);
  357. }
  358. }
  359. }
  360. </script>
  361. <style>
  362. .jessibuca-container {
  363. width: 100%;
  364. height: 100%;
  365. background-color: #000000;
  366. margin: 0 auto;
  367. position: relative;
  368. overflow: hidden;
  369. }
  370. .jessibuca-container.jessibuca-fullscreen {
  371. position: fixed;
  372. top: 0;
  373. left: 0;
  374. width: 100vw !important;
  375. height: 100vh !important;
  376. z-index: 9999;
  377. }
  378. .jessibuca-player-wrapper {
  379. width: 100%;
  380. padding-top: 56.25%;
  381. position: relative;
  382. }
  383. .buttons-box {
  384. width: 100%;
  385. height: 28px;
  386. background-color: rgba(43, 51, 63, 0.7);
  387. position: absolute;
  388. display: -webkit-box;
  389. display: -ms-flexbox;
  390. display: flex;
  391. left: 0;
  392. bottom: 0;
  393. user-select: none;
  394. z-index: 10;
  395. }
  396. .jessibuca-btn {
  397. width: 20px;
  398. color: rgb(255, 255, 255);
  399. line-height: 27px;
  400. margin: 0px 10px;
  401. padding: 0px 2px;
  402. cursor: pointer;
  403. text-align: center;
  404. font-size: 0.8rem !important;
  405. }
  406. .buttons-box-right {
  407. position: absolute;
  408. right: 0;
  409. }
  410. </style>