uniapp实时查看在线监控,JessibucaMobile实现横屏播放

项目中实现点击监控,实现在线监控画面实时查看,但是video组件无法实现,使用JessibucaMobile组件实现,uniappx项目使用h5页面跳转实现:

javascript 复制代码
//  uniapp项目页面
     <template>
  <view class="player-box">
    <web-view update-title="false" :src="webViewUrl"></web-view>
  </view>
</template>

<script>
import { REDIRECT_URL } from '../../api/request.js'
export default {
  data () {
    return {
      accE_URL: REDIRECT_URL,//当前请求地址
      videoUrl: '',
      webViewUrl: '',
      token: ''
    }
  },
  onLoad (options) {
    if (options.videoUrl) {
      const videoUrl = decodeURIComponent(options.videoUrl)
      const token = options.token || uni.getStorageSync('token')
      this.videoUrl = videoUrl
      this.token = token

      // 构建web-view的URL,传递视频地址和token
      this.webViewUrl = `${this.accE_URL}player?videoUrl=${encodeURIComponent(
        videoUrl
      )}`.replace(/\.mp4$/, '.flv') // JessibucaMobile播放只支持flv格式,所以格式需要注意
    } else {
      uni.showToast({
        title: '视频地址无效',
        icon: 'none'
      })
      setTimeout(() => {
        uni.navigateBack()
      }, 1500)
    }
  },
  methods: {
    backClick () {
      uni.switchTab({
        url: '/pages/data/index' // 页面返回跳转的uniapp页面
      })
    },
    onWebViewMessage (e) {
      console.log('收到web-view消息:', e.detail)
    }
  }
}
</script>

<style scoped lang="scss">
.player-box {
  width: 100%;
  height: 100vh;
  background-color: #000;
}
</style>

注意: JessibucaMobile只支持flv格式的文件!!!

javascript 复制代码
// pc端项目------------------------JessibucaMobile.vue
     <template>
  <div class="jessibuca-content-box">
    <div id="container" ref="container">
    </div>
  </div>
</template>
<script setup>
import { nextTick, onMounted, ref, watch } from 'vue'
import { flexbile } from '../utils/flexbile'
const props = defineProps({
  currentUrl: {
    type: String
  },
  currentIndex: {
    type: Number
  }
})
const emits = defineEmits(['clearData'])
const container = ref(null)
const buttonsBox = ref(null)
const currentId = ref(0)
const jessibuca = ref({})
const playing = ref(false)
const loaded = ref(false)
const showOperateBtns = ref(true)
const err = ref('')

const create = options => {
  options = options || {}
  jessibuca.value[currentId.value] = new window.Jessibuca(
    Object.assign(
      {
        container: container.value,
        isResize: false,
        loadingText: '疯狂加载中...',
        debug: true,
        supportDblclickFullscreen: true,
        operateBtns: {
          fullscreen: showOperateBtns.value,
          play: showOperateBtns.value,
          audio: showOperateBtns.value
        },
        isNotMute: true,
        timeout: 10,
        useWebFullScreen: true
      },
      options
    )
  )

  jessibuca.value[currentId.value].on('play', () => {
    playing.value = true
    loaded.value = true
  })
}

const destroy = () => {
  if (jessibuca.value[currentId.value]) {
    jessibuca.value[currentId.value].destroy()
  }
  jessibuca.value[currentId.value] = null
  emits('clearData', currentId.value)
}
watch(
  () => props.currentIndex,
  newVal => {
    currentId.value = newVal
    nextTick(() => {
      if (currentId.value > 0) create()
    })
  },
  { immediate: true, deep: true }
)
watch(
  () => props.currentUrl,
  newVal => {
    console.log('newVal', newVal)
    nextTick(() => {
      if (jessibuca.value[currentId.value]) {
        jessibuca.value[currentId.value].play(newVal)
      }
    })
  },
  { immediate: true, deep: true }
)
defineExpose({
  destroy
})
onMounted(() => {
  flexbile()
})
</script>
<style scoped lang="less">
.jessibuca-content-box {
  width: 100%;
  height: 100%;
}
#container {
  background: rgba(13, 14, 27, 0.7);
  width: 100%;
  height: 100%;
  position: relative;
  .destory-box {
    position: absolute;
    left: 0;
    bottom: 0;
    color: #fff;
    z-index: 9999;
    background-color: #000;
    height: 1.2rem;
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 0.7rem;
    box-sizing: border-box;
    .iconfont {
      font-size: 0.3rem;
      margin-bottom: 0.1rem;
    }
    .box-l,
    .box-c,
    .box-r {
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      font-size: 0.3rem;
    }
  }
}
</style>

上述文件引用的文件 until/flexbile.js

javascript 复制代码
// flexbile.js
   const flexbile = () => {
  var timer = null
  var PAGE_WIDTH = ''
  var PAGE_FONT_SIZE = '100' // 设计稿1rem的大小
  if (
    /phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone/i.test(
      navigator.userAgent
    )
  ) {
    PAGE_WIDTH = 750 // 设计稿的宽度
  } else {
    PAGE_WIDTH = 1920
  }
  function onResize () {
    const screenWidth  = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth
    var e = (PAGE_FONT_SIZE * screenWidth) / PAGE_WIDTH
    console.log(e)
    document.documentElement.style.fontSize = e + 'px'
  }
  window.addEventListener('resize', function () {
    if (timer) clearTimeout(timer)
    timer = setTimeout(onResize, 100)
  })
  onResize()
}

export { flexbile }
javascript 复制代码
//  h5视频播放页面
     <template>
  <div class="fullscreen-player">
    <div class="video-container" :class="{ 'landscape-mode': isLandscape }">
      <!-- 视频播放器 -->
      <JessibucaMobile
        v-if="videoUrl"
        :currentIndex="1"
        :currentUrl="videoUrl"
        @clearData="clearData"
      ></JessibucaMobile>

      <!-- 加载状态 -->
      <div v-if="isLoading" class="loading-indicator">
        <div class="spinner"></div>
        <span>视频加载中...</span>
      </div>

      <!-- 错误提示 -->
      <div v-if="hasError" class="error-message">
        <p>视频加载失败</p>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { useRoute } from 'vue-router'
import JessibucaMobile from '../../components/JessibucaMobile.vue'

const route = useRoute()
const videoPlayer = ref(null)
const videoUrl = ref('')
const isFullscreen = ref(false)
const isPlaying = ref(false)

const isLoading = ref(true)
const hasError = ref(false)
const showControls = ref(true)
const isLandscape = ref(true) // 默认横屏模式

// 隐藏控制栏的定时器
let controlsTimeout = null

// 初始化视频
const initVideo = () => {
  if (route.query.videoUrl) {
    videoUrl.value = decodeURIComponent(route.query.videoUrl)
    console.log('视频地址:', videoUrl.value)
    isLoading.value = false
  } else {
    hasError.value = true
    isLoading.value = false
  }
}

const clearData = () => {
  videoUrl.value = ''
}

// 视频元数据加载完成
const onVideoLoaded = () => {
  isLoading.value = false
  // 尝试自动播放(静音模式下)
  playVideo()
  // 强制横屏显示
  forceLandscape()
}

// 强制横屏显示
const forceLandscape = () => {
  isLandscape.value = true
  // 尝试进入全屏模式
  setTimeout(() => {
    enterFullscreen()
  }, 500)
}

// 进入全屏
const enterFullscreen = () => {
  const video = videoPlayer.value
  if (!video) return

  if (video.requestFullscreen) {
    video.requestFullscreen()
  } else if (video.webkitRequestFullscreen) {
    video.webkitRequestFullscreen()
  } else if (video.mozRequestFullScreen) {
    video.mozRequestFullScreen()
  } else if (video.msRequestFullscreen) {
    video.msRequestFullscreen()
  }

  isFullscreen.value = true
}

// 退出全屏
const exitFullscreen = () => {
  if (document.exitFullscreen) {
    document.exitFullscreen()
  } else if (document.webkitExitFullscreen) {
    document.webkitExitFullscreen()
  } else if (document.mozCancelFullScreen) {
    document.mozCancelFullScreen()
  } else if (document.msExitFullscreen) {
    document.msExitFullscreen()
  }

  isFullscreen.value = false
}

// 切换全屏
const toggleFullscreen = () => {
  if (isFullscreen.value) {
    exitFullscreen()
  } else {
    enterFullscreen()
  }
  resetControlsTimer()
}

// 播放视频
const playVideo = () => {
  const video = videoPlayer.value
  if (video) {
    video
      .play()
      .then(() => {
        isPlaying.value = true
      })
      .catch(error => {
        console.error('播放失败:', error)
        // 如果自动播放失败,显示控制栏让用户手动播放
        showControls.value = true
      })
  }
}

// 重试加载
const retry = () => {
  hasError.value = false
  isLoading.value = true
  const video = videoPlayer.value
  if (video) {
    video.load()
  }
}

// 返回上一页
const goBack = () => {
  if (window.history.length > 1) {
    window.history.back()
  } else {
    window.close()
  }
}

// 重置控制栏隐藏计时器
const resetControlsTimer = () => {
  if (controlsTimeout) {
    clearTimeout(controlsTimeout)
  }
  showControls.value = true
  controlsTimeout = setTimeout(() => {
    showControls.value = false
  }, 3000)
}

// 处理全屏变化事件
const handleFullscreenChange = () => {
  isFullscreen.value = !!(
    document.fullscreenElement ||
    document.webkitFullscreenElement ||
    document.mozFullScreenElement ||
    document.msFullscreenElement
  )
}

// 点击视频区域显示/隐藏控制栏
const toggleControls = () => {
  showControls.value = !showControls.value
  if (showControls.value) {
    resetControlsTimer()
  }
}

onMounted(() => {
  initVideo()

  // 添加全屏变化监听
  document.addEventListener('fullscreenchange', handleFullscreenChange)
  document.addEventListener('webkitfullscreenchange', handleFullscreenChange)
  document.addEventListener('mozfullscreenchange', handleFullscreenChange)
  document.addEventListener('MSFullscreenChange', handleFullscreenChange)

  // 初始隐藏控制栏
  controlsTimeout = setTimeout(() => {
    showControls.value = false
  }, 3000)
})

onUnmounted(() => {
  // 清理事件监听和定时器
  document.removeEventListener('fullscreenchange', handleFullscreenChange)
  document.removeEventListener('webkitfullscreenchange', handleFullscreenChange)
  document.removeEventListener('mozfullscreenchange', handleFullscreenChange)
  document.removeEventListener('MSFullscreenChange', handleFullscreenChange)

  if (controlsTimeout) {
    clearTimeout(controlsTimeout)
  }

  // 退出全屏
  if (isFullscreen.value) {
    exitFullscreen()
  }
})
</script>

<style lang="less" scoped>
.fullscreen-player {
  width: 100vw;
  height: 100vh;
  background: #000;
  overflow: hidden;
  position: fixed;
  top: 0;
  left: 0;
  z-index: 9999;
}

.video-container {
  width: 100%;
  height: 100%;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;

  &.landscape-mode {
    // 强制横屏样式
    transform: rotate(0deg);
  }
}

.video-element {
  width: 100%;
  height: 100%;
  object-fit: contain;
  background: #000;
}

.control-bar {
  position: absolute;
  bottom: 20px;
  left: 50%;
  transform: translateX(-50%);
  background: rgba(0, 0, 0, 0.7);
  padding: 10px 20px;
  border-radius: 20px;
  display: flex;
  gap: 15px;
  align-items: center;
  transition: opacity 0.3s ease;
  z-index: 1000;
}

.control-btn {
  background: rgba(255, 255, 255, 0.2);
  border: none;
  color: white;
  padding: 8px 16px;
  border-radius: 15px;
  cursor: pointer;
  font-size: 14px;
  transition: background 0.3s ease;

  &:hover {
    background: rgba(255, 255, 255, 0.3);
  }
}

.back-btn {
  background: rgba(255, 0, 0, 0.6);
}

.loading-indicator {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  color: white;
  text-align: center;
  z-index: 1000;
  span {
    font-size: 32px;
  }
}

.spinner {
  width: 40px;
  height: 40px;
  border: 4px solid rgba(255, 255, 255, 0.3);
  border-top: 4px solid white;
  border-radius: 50%;
  animation: spin 1s linear infinite;
  margin: 0 auto 10px;
}

@keyframes spin {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}

.error-message {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  color: white;
  text-align: center;
  z-index: 1000;
  p {
    font-size: 32px;
  }
}

.retry-btn {
  background: #007aff;
  color: white;
  border: none;
  padding: 10px 20px;
  border-radius: 15px;
  cursor: pointer;
  margin-top: 10px;
}

// 横屏优化样式
@media (orientation: landscape) {
  .video-container.landscape-mode {
    .video-element {
      object-fit: cover;
    }
  }
}

// 竖屏时的横屏强制样式
@media (orientation: portrait) {
  .video-container.landscape-mode {
    transform: rotate(90deg);
    width: 100vh;
    height: 100vw;
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%) rotate(90deg);
  }
}
</style>

这样,在uniapp端实时查看在线监控视频,横屏展示就实现了。

相关推荐
好想早点睡.8 小时前
vue2+UniApp微信小程序集成高德地图
微信小程序·小程序·uni-app
i小杨8 小时前
React 状态管理库相关收录
前端·react.js·前端框架
Jiaberrr8 小时前
解决uni-app通用上传与后端接口不匹配问题:原生上传文件方法封装 ✨
前端·javascript·uni-app
listhi5208 小时前
CSS:现代Web设计的不同技术
前端·css
南囝coding8 小时前
现代Unix命令行工具革命:30个必备替代品完整指南
前端·后端
起风了___9 小时前
Flutter 多端音频控制台:基于 audio_service 实现 iOS、Android 锁屏与通知中心播放控制
前端·flutter
作业逆流成河9 小时前
🎉 enum-plus 发布新版本了!
前端·javascript·前端框架
WYiQIU9 小时前
高级Web前端开发工程师2025年面试题总结及参考答案【含刷题资源库】
前端·vue.js·面试·职场和发展·前端框架·reactjs·飞书
WuWuII9 小时前
SSE服务端单向推送消息到前端
前端·推送