video-player.vue 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. <template>
  2. <view class="video-player-container">
  3. <!-- H5环境使用Jessibuca -->
  4. <view v-if="isH5" class="h5-player">
  5. <view ref="jessibucaContainer" class="jessibuca-container"></view>
  6. <view v-if="loadError" class="error-message">
  7. <text>视频加载失败,请刷新重试</text>
  8. </view>
  9. </view>
  10. <!-- 小程序环境使用live-player -->
  11. <view v-else class="mp-player">
  12. <live-player
  13. id="videoPlayer"
  14. :src="videoUrl"
  15. mode="live"
  16. :autoplay="autoplay"
  17. :muted="isMuted"
  18. object-fit="contain"
  19. @statechange="onStateChange"
  20. @error="onError"
  21. @fullscreenchange="onFullscreenChange"
  22. class="live-player"
  23. ></live-player>
  24. </view>
  25. </view>
  26. </template>
  27. <script setup>
  28. import { ref, watch, onMounted, onBeforeUnmount, nextTick, getCurrentInstance } from 'vue'
  29. // Props
  30. const props = defineProps({
  31. videoUrl: {
  32. type: String,
  33. required: true
  34. },
  35. hasAudio: {
  36. type: Boolean,
  37. default: true
  38. },
  39. autoplay: {
  40. type: Boolean,
  41. default: true
  42. }
  43. })
  44. // Emits
  45. const emit = defineEmits(['play', 'pause', 'error', 'fullscreenchange'])
  46. // Template refs
  47. const jessibucaContainer = ref(null)
  48. // Reactive data
  49. const isH5 = ref(false)
  50. const isMuted = ref(false)
  51. const isPlaying = ref(false)
  52. const isFullscreen = ref(false)
  53. const jessibucaPlayer = ref(null)
  54. const livePlayerContext = ref(null)
  55. const loadError = ref(false)
  56. // Get current instance for accessing global properties
  57. const instance = getCurrentInstance()
  58. // 初始化Jessibuca播放器(H5环境)
  59. /* const initJessibucaPlayer = () => {
  60. if (!isH5.value || !instance?.appContext.config.globalProperties.$jessibuca) {
  61. console.error('H5环境或Jessibuca插件未初始化')
  62. loadError.value = true
  63. return
  64. }
  65. console.log('正在初始化Jessibuca播放器,URL:', props.videoUrl)
  66. // 使用Jessibuca插件
  67. instance.appContext.config.globalProperties.$jessibuca.createPlayer({
  68. container: jessibucaContainer.value,
  69. url: props.videoUrl,
  70. hasAudio: props.hasAudio,
  71. autoplay: props.autoplay,
  72. decoder: './static/js/jessibuca/decoder.js',
  73. wasmUrl: './static/js/jessibuca/decoder.wasm'
  74. }).then(player => {
  75. if (!player) {
  76. console.error('Jessibuca播放器创建失败')
  77. loadError.value = true
  78. return
  79. }
  80. jessibucaPlayer.value = player
  81. console.log('Jessibuca播放器创建成功')
  82. // 监听事件
  83. player.on('play', () => {
  84. isPlaying.value = true
  85. loadError.value = false
  86. emit('play')
  87. })
  88. player.on('pause', () => {
  89. isPlaying.value = false
  90. emit('pause')
  91. })
  92. player.on('error', (err) => {
  93. console.error('Jessibuca播放错误:', err)
  94. loadError.value = true
  95. emit('error', err)
  96. })
  97. }).catch(err => {
  98. console.error('Jessibuca播放器初始化失败:', err)
  99. loadError.value = true
  100. })
  101. } */
  102. // 初始化小程序live-player
  103. const initLivePlayer = () => {
  104. if (isH5.value) return
  105. // #ifdef MP-WEIXIN
  106. livePlayerContext.value = uni.createLivePlayerContext('videoPlayer', instance)
  107. if (props.autoplay) {
  108. play()
  109. }
  110. // #endif
  111. }
  112. // 播放
  113. const play = () => {
  114. if (isH5.value && jessibucaPlayer.value) {
  115. jessibucaPlayer.value.play()
  116. } else if (livePlayerContext.value) {
  117. livePlayerContext.value.play({
  118. success: () => {
  119. isPlaying.value = true
  120. emit('play')
  121. },
  122. fail: (err) => {
  123. console.error('播放失败:', err)
  124. emit('error', err)
  125. }
  126. })
  127. }
  128. }
  129. // 暂停
  130. const pause = () => {
  131. if (isH5.value && jessibucaPlayer.value) {
  132. jessibucaPlayer.value.pause()
  133. } else if (livePlayerContext.value) {
  134. livePlayerContext.value.pause({
  135. success: () => {
  136. isPlaying.value = false
  137. emit('pause')
  138. }
  139. })
  140. }
  141. }
  142. // 截图
  143. const screenshot = () => {
  144. if (isH5.value && jessibucaPlayer.value) {
  145. return jessibucaPlayer.value.screenshot()
  146. } else if (livePlayerContext.value) {
  147. return new Promise((resolve, reject) => {
  148. livePlayerContext.value.snapshot({
  149. success: (res) => {
  150. resolve(res.tempImagePath)
  151. },
  152. fail: (err) => {
  153. reject(err)
  154. }
  155. })
  156. })
  157. }
  158. }
  159. // 静音
  160. const mute = () => {
  161. isMuted.value = true
  162. if (isH5.value && jessibucaPlayer.value) {
  163. jessibucaPlayer.value.mute()
  164. }
  165. }
  166. // 取消静音
  167. const cancelMute = () => {
  168. isMuted.value = false
  169. if (isH5.value && jessibucaPlayer.value) {
  170. jessibucaPlayer.value.cancelMute()
  171. }
  172. }
  173. // 全屏
  174. const fullscreenSwich = () => {
  175. if (isH5.value && jessibucaPlayer.value) {
  176. jessibucaPlayer.value.fullscreen()
  177. } else if (livePlayerContext.value) {
  178. if (isFullscreen.value) {
  179. livePlayerContext.value.exitFullScreen()
  180. } else {
  181. livePlayerContext.value.requestFullScreen({
  182. direction: 90
  183. })
  184. }
  185. }
  186. }
  187. // 重置大小
  188. const resize = () => {
  189. if (isH5.value && jessibucaPlayer.value) {
  190. jessibucaPlayer.value.resize()
  191. }
  192. }
  193. // 检查是否全屏
  194. const isFullscreenCheck = () => {
  195. return isFullscreen.value
  196. }
  197. // live-player状态变化
  198. const onStateChange = (e) => {
  199. console.log('播放器状态变化:', e.detail)
  200. const state = e.detail.code
  201. if (state === 2003) { // 播放中
  202. isPlaying.value = true
  203. emit('play')
  204. } else if (state === 2004) { // 暂停
  205. isPlaying.value = false
  206. emit('pause')
  207. }
  208. }
  209. // live-player错误
  210. const onError = (e) => {
  211. console.error('播放器错误:', e.detail)
  212. emit('error', e.detail)
  213. }
  214. // 全屏状态变化
  215. const onFullscreenChange = (e) => {
  216. isFullscreen.value = e.detail.fullScreen
  217. emit('fullscreenchange', isFullscreen.value)
  218. }
  219. // 销毁播放器
  220. const destroyPlayer = () => {
  221. if (isH5.value && jessibucaPlayer.value) {
  222. jessibucaPlayer.value.destroy()
  223. jessibucaPlayer.value = null
  224. }
  225. }
  226. // Watch videoUrl changes
  227. watch(() => props.videoUrl, () => {
  228. if (isH5.value && jessibucaPlayer.value) {
  229. destroyPlayer()
  230. nextTick(() => {
  231. // initJessibucaPlayer()
  232. })
  233. }
  234. })
  235. // Lifecycle hooks
  236. onMounted(() => {
  237. // 判断当前环境
  238. // #ifdef H5
  239. isH5.value = true
  240. nextTick(() => {
  241. // initJessibucaPlayer()
  242. })
  243. // #endif
  244. // #ifdef MP-WEIXIN
  245. isH5.value = false
  246. initLivePlayer()
  247. // #endif
  248. })
  249. onBeforeUnmount(() => {
  250. destroyPlayer()
  251. })
  252. // Expose methods for parent component access
  253. defineExpose({
  254. play,
  255. pause,
  256. screenshot,
  257. mute,
  258. cancelMute,
  259. fullscreenSwich,
  260. resize,
  261. isFullscreen: isFullscreenCheck
  262. })
  263. </script>
  264. <style>
  265. .video-player-container {
  266. width: 100%;
  267. height: 100%;
  268. background-color: #000;
  269. position: relative;
  270. }
  271. .h5-player, .mp-player, .jessibuca-container, .live-player {
  272. width: 100%;
  273. height: 100%;
  274. }
  275. .error-message {
  276. position: absolute;
  277. top: 50%;
  278. left: 50%;
  279. transform: translate(-50%, -50%);
  280. color: #fff;
  281. background-color: rgba(0, 0, 0, 0.5);
  282. padding: 10px 20px;
  283. border-radius: 4px;
  284. }
  285. </style>