最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

基于Vue3+西瓜視頻播放器(xgplayer)移動(dòng)端直播解決方案

 更新時(shí)間:2026年02月12日 10:56:52   作者:胖虎265  
在現(xiàn)代前端開(kāi)發(fā)中,視頻播放功能越來(lái)越常見(jiàn),從在線教育到娛樂(lè)平臺(tái)都離不開(kāi)高質(zhì)量的視頻播放器,這篇文章主要介紹了基于Vue3+西瓜視頻播放器(xgplayer)移動(dòng)端直播的相關(guān)資料,需要的朋友可以參考下

基于 Vue3 + 西瓜視頻播放器(xgplayer),打造輕量、流暢、可直接復(fù)用的移動(dòng)端直播組件,適配快速集成場(chǎng)景。

最終效果

  • ?? 支持 HLS(m3u8)直播流,播放流暢無(wú)卡頓
  • ?? 自定義封面 + 播放按鈕,點(diǎn)擊即啟動(dòng)
  • ?? 移動(dòng)端適配(內(nèi)嵌播放、全屏切換、無(wú)滾動(dòng)條)
  • ?? 基礎(chǔ)錯(cuò)誤處理,兼容瀏覽器自動(dòng)播放限制

核心代碼實(shí)現(xiàn)

1. 依賴安裝

npm install xgplayer --save

2. 極簡(jiǎn)直播播放器組件(LivePlayer.vue)

<template>
  <div :id="id" style="position: relative" :width="props.width" :height="props.height">
    <img
      v-if="showImage"
      style="background-color: black; object-fit: cover"
      :width="props.width"
      :height="props.height"
      :src="props.poster"
      @click="clickImage"
    />
    <img
      v-if="showImage"
      class="play-icon"
      :src="getAssetsImages(`icon_play.png`)"
      alt=""
      @click="clickImage"
    />
  </div>
</template>

<script setup lang="ts">
import { getAssetsImages } from "@/utils/util.js";
import { ref } from "vue";
import Player from "xgplayer";
import "xgplayer/dist/index.min.css";

const props = defineProps({
  id: {
    type: String,
    required: true,
  },
  videoUrl: {
    type: String,
    default: () => "",
  },
  poster: {
    type: String,
    default: () => "",
  },
  playsinline: {
    type: Boolean,
    default: true,
  },
  width: {
    type: String,
    default: "100%",
  },
  height: {
    type: String,
    default: "100%",
  },
});

const showImage = ref(true);
// 定義一個(gè)變量來(lái)存儲(chǔ) player 實(shí)例
let player: Player;

const clickImage = () => {
  if (player == null) {
    initPlayer();
    showImage.value = false;
  }
};

// 初始化西瓜視頻
const initPlayer = () => {
  player = new Player({
    lang: "zh", // 設(shè)置播放器的語(yǔ)言為中文(zh)
    volume: 0.5, // 設(shè)置初始音量為50%
    id: props.id, // 使用傳入的id屬性,可能是一個(gè)容器或視頻元素的ID
    url: props.videoUrl, // 設(shè)置視頻的URL地址
    poster: props.poster, // 設(shè)置視頻封面圖
    playsinline: props.playsinline, // 是否允許在移動(dòng)設(shè)備上內(nèi)嵌播放(不跳轉(zhuǎn)到全屏)
    height: props.height, // 設(shè)置播放器的高度
    width: props.width, // 設(shè)置播放器的寬度
    isLive: true, // 設(shè)置是否直播
    playbackRate: [1], // 倍速展示
    defaultPlaybackRate: 1,
    cssFullscreen: false, // 設(shè)置是否使用CSS全屏樣式
    download: false, // 隱藏下載按鈕
    autoplay: false, // 自動(dòng)播放視頻
    whitelist: [""], // 設(shè)置白名單,當(dāng)前為空
  });
  // 添加事件監(jiān)聽(tīng)器,確保播放器準(zhǔn)備好后再播放
  player.on("ready", () => {
    player.play(); // 播放視頻
  });
};
</script>

<style scoped>
.play-icon {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 50px;
  height: 50px;
}
</style>

3. 頁(yè)面使用示例(LivePage.vue)

<template>
  <div class="award-ceremony">
    <div class="award-ceremony__content">
      <!-- 直播播放器區(qū)域 -->
      <div class="video_wrapper">
        <VideoPlayer
          id="videoPlayer"
          :live-url="liveStreamUrl"
          :poster="livePoster"
        />
      </div>

      <!-- 主體內(nèi)容區(qū)(評(píng)論/節(jié)目單) -->
      <div class="main_wrapper">
        <!-- 切換按鈕 -->
        <ul class="btn-list">
          <li
            v-for="(item, index) in btnList"
            :key="index"
            :class="index === activeIndex ? 'active' : ''"
            @click="handleTabClick(index)"
          >
            {{ item }}
          </li>
        </ul>

        <!-- 評(píng)論列表 -->
        <ul class="comment-list">
          <van-pull-refresh v-model="refreshing" success-text="刷新成功" @refresh="onRefresh">
            <van-list
              v-model:loading="loading"
              offset="100"
              :finished="finished"
              finished-text="沒(méi)有更多了"
              @load="onLoad"
            >
              <li v-for="(item, index) in commentList" :key="index" class="comment-item">
                <!-- 評(píng)論用戶信息 -->
                <div class="comment-user">
                  <div class="name">{{ item.userName }}</div>
                  <div class="from" v-if="item.userType === 'leader'">
                    {{ item.orgName }}
                  </div>
                  <div class="from" v-else>
                    {{ item.company }}
                  </div>
                  <div class="from" v-else-if="item.department">{{ item.department }}</div>
                </div>
                <!-- 評(píng)論內(nèi)容 -->
                <div class="comment-content">
                  <div class="text">
                    <span>{{ item.content?.substring(0, 64) }}</span>
                    <span v-show="item.isExpand">{{ item.content?.substring(64) }}</span>
                    <span v-if="item.content?.length > 64 && !item.isExpand">...</span>
                    <span v-if="item.content?.length > 64" class="more" @click="toggleExpand(item)">
                      {{ item.isExpand ? "收起" : "展開(kāi)" }}
                    </span>
                  </div>
                  <div class="time">{{ formatTime(item.publishTime) }}</div>
                </div>
              </li>
            </van-list>
          </van-pull-refresh>
        </ul>

        <!-- 評(píng)論發(fā)布框 -->
        <van-cell-group>
          <van-field
            v-model="commentInput"
            maxlength="100"
            rows="1"
            autosize
            label=""
            type="textarea"
            placeholder="請(qǐng)輸入(最多100字)"
          >
            <template #button>
              <van-button size="small" :loading="isPublishing" @click="publishComment">發(fā)送</van-button>
            </template>
          </van-field>
        </van-cell-group>
      </div>
    </div>

    <!-- 節(jié)目單彈窗 -->
    <van-overlay
      z-index="999"
      :show="showProgramModal"
      :lock-scroll="false"
      @click="showProgramModal = false"
    >
      <div class="program-wrapper">
        <div class="program-bg">
          <div class="program-detail" @click.stop>
            <div class="title text-gradient">標(biāo)題占位</div>
            <div class="subtitle text-gradient">企業(yè)20xx年度先進(jìn)頒獎(jiǎng)典禮</div>
            <van-list class="program-list">
              <div v-for="(item, index) in programList" :key="index">
                <div v-if="item.isImportant" class="important-item">
                  <p>{{ item.programType }}</p>
                  <p>{{ item.programName }}</p>
                  <p>{{ item.host }}</p>
                </div>
                <div v-else class="program-item">
                  <p>{{ item.programType }}{{ item.programName }}</p>
                  <p>{{ item.host }}</p>
                </div>
                <div class="award-item">{{ item.awardDesc }}</div>
              </div>
            </van-list>
          </div>
        </div>
      </div>
    </van-overlay>
  </div>
</template>

<script setup>
import { ref, watch, onMounted } from "vue";
import VideoPlayer from "./VideoPlayer.vue"; // 引入播放器組件(下文附簡(jiǎn)化版)
import { VanPullRefresh, VanList, VanCellGroup, VanField, VanButton, VanOverlay, VanToast } from "vant";
import "vant/lib/index.css";

// 直播核心配置(替換為實(shí)際項(xiàng)目配置)
const liveStreamUrl = ref("https://live-stream.example.com/livestream.m3u8"); // 直播流地址(m3u8)
const livePoster = ref("https://picsum.photos/1080/720"); // 直播封面圖

// 標(biāo)簽切換
const btnList = ref(["評(píng)論", "節(jié)目單"]);
const activeIndex = ref(0);
const showProgramModal = ref(false);

// 評(píng)論模塊狀態(tài)
const commentList = ref([]);
const commentInput = ref("");
const loading = ref(false);
const finished = ref(false);
const refreshing = ref(false);
const isPublishing = ref(false);
let page = 1;
const pageSize = 5;

// 節(jié)目單數(shù)據(jù)(實(shí)際項(xiàng)目從接口獲取)
const programList = ref([
  {
    isImportant: true,
    programType: "開(kāi)場(chǎng)",
    programName: "領(lǐng)導(dǎo)致辭",
    host: "張總",
    awardDesc: "歡迎各位嘉賓蒞臨本次頒獎(jiǎng)典禮"
  },
  {
    isImportant: false,
    programType: "頒獎(jiǎng)",
    programName: "年度優(yōu)秀員工",
    host: "李經(jīng)理",
    awardDesc: "表彰本年度表現(xiàn)突出的優(yōu)秀員工代表"
  },
  {
    isImportant: false,
    programType: "表演",
    programName: "員工才藝展示",
    host: "王主持人",
    awardDesc: "員工自發(fā)組織的文藝表演"
  },
  {
    isImportant: true,
    programType: "閉幕",
    programName: "總結(jié)發(fā)言",
    host: "劉總",
    awardDesc: "本次頒獎(jiǎng)典禮總結(jié)及未來(lái)展望"
  }
]);

/** 切換標(biāo)簽(評(píng)論/節(jié)目單) */
const handleTabClick = (index) => {
  activeIndex.value = index;
  if (index === 1) {
    showProgramModal.value = true;
  }
};

/** 加載評(píng)論列表(模擬接口請(qǐng)求) */
const onLoad = async () => {
  if (finished.value) return;
  loading.value = true;

  try {
    // 模擬接口請(qǐng)求(實(shí)際項(xiàng)目替換為真實(shí)接口)
    await new Promise(resolve => setTimeout(resolve, 800));
    
    // 模擬評(píng)論數(shù)據(jù)
    const mockComments = Array.from({ length: pageSize }, (_, i) => ({
      userName: `用戶${page * pageSize + i + 1}`,
      userType: Math.random() > 0.7 ? "leader" : "normal",
      orgName: Math.random() > 0.7 ? "企業(yè)總部" : "分公司",
      company: "中國(guó)電信",
      department: "技術(shù)部",
      content: `恭喜獲獎(jiǎng)的同事!${"直播內(nèi)容非常精彩,為奮斗者點(diǎn)贊~".repeat(Math.floor(Math.random() * 3) + 1)}`,
      publishTime: new Date().toISOString(),
      isExpand: false
    }));

    if (refreshing.value) {
      commentList.value = mockComments;
      refreshing.value = false;
    } else {
      commentList.value.push(...mockComments);
    }

    // 模擬加載完成(第3頁(yè)后無(wú)更多數(shù)據(jù))
    if (page >= 3) {
      finished.value = true;
    }
    page++;
  } catch (error) {
    console.error("加載評(píng)論失?。?, error);
    VanToast("加載失敗,請(qǐng)稍后重試");
  } finally {
    loading.value = false;
  }
};

/** 刷新評(píng)論列表 */
const onRefresh = () => {
  finished.value = false;
  commentList.value = [];
  page = 1;
  loading.value = true;
  onLoad();
};

/** 發(fā)布評(píng)論 */
const publishComment = async () => {
  const content = commentInput.value.trim();
  if (!content) return;

  isPublishing.value = true;
  try {
    // 模擬發(fā)布接口請(qǐng)求
    await new Promise(resolve => setTimeout(resolve, 500));
    
    // 發(fā)布成功后添加到列表頭部
    commentList.value.unshift({
      userName: "當(dāng)前用戶",
      userType: "normal",
      company: "中國(guó)電信",
      department: "市場(chǎng)部",
      content: content,
      publishTime: new Date().toISOString(),
      isExpand: false
    });
    commentInput.value = "";
    VanToast("發(fā)布成功");
  } catch (error) {
    console.error("發(fā)布評(píng)論失?。?, error);
    VanToast("發(fā)布失敗,請(qǐng)稍后重試");
  } finally {
    isPublishing.value = false;
  }
};

/** 展開(kāi)/收起長(zhǎng)評(píng)論 */
const toggleExpand = (item) => {
  item.isExpand = !item.isExpand;
};

/** 格式化時(shí)間 */
const formatTime = (timeStr) => {
  if (!timeStr) return "";
  const date = new Date(timeStr);
  return date.toLocaleString("zh-CN", {
    month: "2-digit",
    day: "2-digit",
    hour: "2-digit",
    minute: "2-digit"
  });
};

/** 監(jiān)聽(tīng)節(jié)目單彈窗,加載數(shù)據(jù)(實(shí)際項(xiàng)目可在此處請(qǐng)求最新節(jié)目單) */
watch(showProgramModal, (isShow) => {
  if (isShow) {
    document.body.style.overflow = "hidden";
  } else {
    document.body.style.overflow = "";
  }
});

/** 頁(yè)面掛載時(shí)加載評(píng)論 */
onMounted(() => {
  onLoad();
});
</script>

<style lang="scss" scoped>
@font-face {
  font-family: "wdch";
  src: url("https://cdn.example.com/fonts/wdch.ttf") format("truetype"); // 替換為公開(kāi)字體CDN
}

.award-ceremony {
  overflow: hidden;
  height: 100vh;
  font-size: 12px;
  position: relative;

  :deep(.van-hairline--top-bottom:after) {
    border: none;
  }

  &__content {
    height: 100vh;
    background: linear-gradient(180deg, #fff3e6 0%, #ffffff 100%);

    .video_wrapper {
      width: 100%;
      height: 210px;
      background: #ca1e00 url("https://picsum.photos/1080/210") no-repeat center bottom; // 替換為公開(kāi)背景圖
      background-size: 100% auto;
      overflow: hidden;
    }

    .main_wrapper {
      height: calc(100% - 210px);
      display: flex;
      flex-direction: column;

      .btn-list {
        flex: none;
        display: flex;
        justify-content: space-around;
        align-items: center;
        padding: 4px 0;
        background: #fff;

        li {
          width: 50%;
          height: 100%;
          line-height: 26px;
          text-align: center;
          color: #333;
          font-size: 14px;
          cursor: pointer;

          &.active {
            font-size: 16px;
            color: #e33016;
            background: url("https://picsum.photos/200/30") no-repeat center bottom; // 替換為公開(kāi)按鈕背景
            background-size: auto 100%;
          }
        }
      }

      .comment-list {
        flex: auto;
        overflow-y: auto;
        padding: 0 12px;
        box-sizing: border-box;

        .comment-item {
          margin: 8px 0 0;
          padding: 12px 0;
          min-height: 96px;
          background: linear-gradient(90deg, #ffd3c4, #ffffff, #ffffff);
          background-size: 100% 100%;
          display: flex;
          align-items: center;
          border-radius: 4px;
          border: 1px solid #ffc9c1;

          .comment-user {
            padding: 0 8px;
            font-size: 14px;
            color: #da2a0e;
            line-height: 24px;
            width: 33.33%;
            text-align: center;

            .from {
              font-size: 12px;
            }
          }

          .comment-content {
            height: 100%;
            padding: 0 8px;
            font-size: 12px;
            color: #131415;
            line-height: 14px;
            width: 66.66%;
            border-left: 1px dashed rgba(227, 48, 22, 0.2);

            .text {
              min-height: 56px;

              span {
                word-wrap: break-word;
                white-space: normal;
              }

              .more {
                color: #666;
                margin-left: 4px;
                white-space: nowrap;
                cursor: pointer;
              }
            }

            .time {
              text-align: right;
              position: relative;
              top: 4px;
              color: #999;
            }
          }
        }
      }

      :deep(.van-cell-group) {
        flex: none;
        width: 100%;
        padding: 8px 12px;
        border-top: 1px solid #f2f3f5;
        box-shadow: 0 0 5px #f2f3f5;
        background: #fff;

        .van-field__body {
          align-items: flex-end;
        }

        .van-field__control {
          background: rgba(255, 207, 197, 0.9);
          box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.2);
          border-radius: 16px;
          padding: 8px;
          font-size: 12px;
          line-height: 16px;
          min-height: 32px;
          color: #e53416;
          word-wrap: break-word;
          white-space: normal;
          overflow: hidden;
        }

        .van-button {
          width: 64px;
          height: 32px;
          background: linear-gradient(180deg, #f35e31 0%, #e33016 100%);
          border-radius: 16px;
          font-size: 14px;
          color: #fffefc;
        }
      }
    }
  }

  .program-wrapper {
    width: 100%;
    height: 100%;
    padding: 46px 16px 24px; // 適配導(dǎo)航欄高度

    .program-bg {
      width: 100%;
      height: calc(100vh - 46px - 24px);
      background: url("https://picsum.photos/1080/720") no-repeat center bottom; // 替換為公開(kāi)節(jié)目單背景
      box-sizing: border-box;
      background-size: 100% 100%;
      text-align: center;
      padding: 19vh 12px 4vh;
    }

    .program-detail {
      width: 100%;
      height: 100%;
    }

    .title {
      height: 32px;
      line-height: 40px;
      font-size: 18px;
      font-family: "wdch";
    }

    .subtitle {
      line-height: 40px;
      font-size: 15px;
      font-family: "wdch";
    }

    .program-list {
      height: calc(100% - 72px);
      overflow-y: auto;
      color: #e33016;
      font-size: 14px;
      margin: 0 -8px 0 0;

      .program-item {
        text-align: center;
        line-height: 20px;
        padding: 4px 0;
      }

      .award-item {
        line-height: 40px;
        font-size: 15px;
        font-family: "wdch";
        background: linear-gradient(180deg, #ff0000 0%, #ff8300 100%);
        -webkit-background-clip: text;
        -webkit-text-fill-color: transparent;
      }

      .important-item {
        padding: 4px 0;

        > p {
          font-size: 15px;
          font-family: "wdch";
          background: linear-gradient(180deg, #ff0000 0%, #ff8300 100%);
          -webkit-background-clip: text;
          -webkit-text-fill-color: transparent;
        }
      }
    }

    .text-gradient {
      background: linear-gradient(180deg, #f0855c 0%, #ef2407 100%);
      -webkit-background-clip: text;
      -webkit-text-fill-color: transparent;
    }
  }
}
</style>

總結(jié) 

到此這篇關(guān)于基于Vue3+西瓜視頻播放器(xgplayer)移動(dòng)端直播解決方案的文章就介紹到這了,更多相關(guān)Vue3 xgplayer移動(dòng)端直播內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論

环江| 石渠县| 安新县| 咸丰县| 普格县| 什邡市| 无锡市| 和林格尔县| 松江区| 扬中市| 平潭县| 广昌县| 延边| 瓮安县| 黄梅县| 颍上县| 芷江| 汶上县| 安图县| 买车| 汉源县| 郑州市| 明星| 隆化县| 康马县| 张家港市| 前郭尔| 合山市| 星座| 庆元县| 临潭县| 蒙阴县| 横山县| 开化县| 上蔡县| 靖远县| 阿尔山市| 金阳县| 惠水县| 罗江县| 钦州市|