观众观看(H5 移动端浏览器)

最近更新时间:2025-10-16 11:11:52

我的收藏

概述

本文对 TUILiveKit Demo 中的观看页面进行了详细的介绍,您可以在已有项目中直接参考本文档集成我们开发好的观看页面,也可以根据您的需求按照文档中的内容对页面的样式,布局以及功能项进行深度的定制。

功能概览

功能分类
具体能力
直播视频播放
高清流畅的直播体验
弹幕互动
实时弹幕交流
观众列表
查看在线观众信息
关注主播
一键关注喜欢的主播
全屏播放
沉浸式观看体验
Web 桌面浏览器支持
跨平台兼容

功能展示

观看页面提供默认行为和风格,但如果默认行为和样式不能完全满足您的需求,您也可以对 UI 进行自定义。图中显示的数字与特定功能列表中的类别相对应。其中主要包含直播信息展示、视频直播区域、在线观众、音视频操作、直播时长、画面分辨率切换、全屏功能、聊天互动、消息列表等。


快速接入

步骤1:环境配置及开通服务

在进行快速接入之前,您需要参见 准备工作,满足相关环境配置及开通对应服务。

步骤2: 安装依赖

npm
pnpm
yarn
npm install tuikit-atomicx-vue3 @tencentcloud/uikit-base-component-vue3 --save
pnpm add tuikit-atomicx-vue3 @tencentcloud/uikit-base-component-vue3
yarn add tuikit-atomicx-vue3 @tencentcloud/uikit-base-component-vue3

步骤3: 观看页面接入

在您的项目下创建 live-player.vue 文件,可直接复制如下代码至您的项目中集成观看页面

<template>
<UIKitProvider language="zh-CN" theme="dark">
<div class="live-container">
<!-- 直播核心区域 -->
<section class="live-main">
<!-- 顶部信息栏 -->
<div class="top-bar">
<div class="top-left">
<Avatar
:src="currentLive?.liveOwner.avatarUrl"
:size="24"
class="avatar"
/>
<span class="user-name">
{{ currentLive?.liveOwner.userName || currentLive?.liveOwner.userId }}
</span>
<div class="follow-btn" @click="handleFollow">
<span>关注</span>
</div>
</div>
<div class="top-right">
<div class="audience-preview" @click="showAudienceDrawer">
<Avatar
v-for="(user, index) in audienceList.slice(0, 3)"
:key="user.userId || index"
:src="user.avatarUrl"
:size="24"
class="audience-avatar"
/>
<div class="audience-count">
<span>{{ audienceList.length }}</span>
</div>
</div>
<IconClose class="back-btn" size="20" @click="handleBack" />
</div>
</div>

<!-- 直播播放器 -->
<div class="stream-container">
<LiveCoreView class="player" />
</div>

<!-- 悬浮消息列表 -->
<div class="floating-message-list">
<BarrageList class="message-list" />
</div>

<!-- 底部消息输入 -->
<div class="bottom-input">
<div class="message-input-wrapper">
<BarrageInput
:width="inputWidth"
height="44px"
:placeholder="'说点什么...'"
:autoFocus="false"
:disabled="!isLiveActive"
/>
</div>
</div>
</section>

<!-- 观众列表抽屉 -->
<div v-if="audienceDrawerVisible" class="audience-drawer-overlay" @click="closeAudienceDrawer">
<div class="audience-drawer" @click.stop>
<div class="drawer-header">
<h3>在线观众 ({{ audienceList.length }})</h3>
</div>
<div class="drawer-content">
<LiveAudienceList class="audience-list" />
<div v-if="audienceList.length === 0" class="empty-audience">
<p>暂无观众在线</p>
</div>
</div>
</div>
</div>

<!-- 直播结束遮罩 -->
<div v-if="liveEndVisible" class="live-end-overlay">
<div class="live-end-content">
<div class="end-title">
<span>直播已结束</span>
</div>
<Avatar
:src="currentLive?.liveOwner.avatarUrl"
:size="85"
class="end-avatar"
/>
<span class="end-name">{{ currentLive?.liveOwner.userName || currentLive?.liveOwner.userId }}</span>
</div>
</div>
</div>
</UIKitProvider>
</template>

<script setup lang="ts">
import { onMounted, ref, computed } from 'vue';
import {
LiveAudienceList,
BarrageList,
BarrageInput,
useLiveAudienceState,
LiveCoreView,
useLiveState,
Avatar,
useLoginState
} from 'tuikit-atomicx-vue3';
import { UIKitProvider, IconClose } from '@tencentcloud/uikit-base-component-vue3';

const { audienceList, fetchAudienceList } = useLiveAudienceState();
const { currentLive } = useLiveState();
const { login } = useLoginState();

const audienceDrawerVisible = ref(false);
const liveEndVisible = ref(false);
const isLiveActive = ref(true);

const inputWidth = computed(() => {
return window.innerWidth > 768 ? '200px' : '158px';
});

async function initLogin() {
try {
await login({
sdkAppId: 0, // SDKAppID, 可以参考步骤 1 获取
userId: '', // UserID, 可以参考步骤 1 获取
userSig: '', // userSig, 可以参考步骤 1 获取
});
} catch (error) {
console.error('登录失败:', error);
}
}

function handleBack() {
console.log('返回上一页');
}

function handleFollow() {
console.log('关注主播');
}

async function showAudienceDrawer() {
await fetchAudienceList();
audienceDrawerVisible.value = true;
}

function closeAudienceDrawer() {
audienceDrawerVisible.value = false;
}

onMounted(async () => {
await initLogin();
});
</script>

<style scoped>
:global(body){height:100vh;width:100vw;margin:0;padding:0;overflow:hidden;font-size:14px;line-height:1.6;text-rendering:optimizeLegibility;}:global(*),:global(*::before),:global(*::after){box-sizing:border-box;margin:0;}.live-container{position:fixed;display:flex;justify-content:center;align-items:center;width:100%;height:100%;background-color:black;color:var(--text-color1,#fff);}.live-main{width:100%;height:100%;position:relative;background-color:var(--uikit-color-bg-color-dialog);}.top-bar{position:absolute;top:10px;left:0;right:0;display:flex;justify-content:space-between;align-items:center;z-index:100;height:50px;padding:0 10px;}.top-left{display:flex;align-items:center;gap:5px;max-width:55%;padding:5px;background-color:var(--uikit-color-black-6);border-radius:25px;backdrop-filter:blur(10px);overflow:hidden;}.back-btn{cursor:pointer;color:var(--text-color1,#fff);transition:color 0.2s;flex-shrink:0;}.back-btn:hover{color:#4086FF;}.avatar{border:1px solid var(--uikit-color-white-7);flex-shrink:0;}.user-name{color:var(--text-color1,#fff);font-weight:500;font-size:14px;max-width:60%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}.follow-btn{display:flex;align-items:center;justify-content:center;width:50px;max-width:100px;height:24px;background-color:#4086FF;border-radius:12px;cursor:pointer;transition:background-color 0.2s;flex-shrink:0;overflow:hidden;}.follow-btn:hover{background-color:#2B6AD6;}.follow-btn span{font-size:14px;color:var(--text-color1,#fff);}.top-right{display:flex;align-items:center;gap:5px;overflow:hidden;}.audience-preview{display:flex;align-items:center;gap:1px;cursor:pointer;}.audience-avatar{border:1px solid var(--uikit-color-white-7);}.audience-count{display:flex;align-items:center;justify-content:center;width:24px;height:24px;background-color:var(--uikit-color-black-6);border-radius:50%;text-align:center;color:var(--text-color1,#fff);overflow:hidden;}.audience-count span{flex:1;font-size:12px;}.close-btn{display:flex;align-items:center;justify-content:center;width:32px;height:32px;background-color:var(--uikit-color-black-6);border-radius:50%;cursor:pointer;color:var(--text-color1,#fff);transition:background-color 0.2s;}.close-btn:hover{background-color:rgba(255,255,255,0.2);}.stream-container{width:100%;height:100%;display:flex;align-items:center;justify-content:center;}.player{width:100%;height:100%;background-color:black;}.floating-message-list{position:absolute;left:0;bottom:60px;z-index:99;pointer-events:none;}.message-list{width:100%;height:100%;overflow-y:auto;padding:8px;}.bottom-input{position:absolute;bottom:10px;left:0;right:0;display:flex;align-items:center;justify-content:space-between;height:48px;padding:0 10px;z-index:100;}.message-input-wrapper{display:flex;align-items:center;gap:8px;}.input-actions{display:flex;align-items:center;gap:4px;}.emoji-btn,.gift-btn{display:flex;align-items:center;justify-content:center;width:36px;height:36px;background-color:var(--uikit-color-black-6);border:none;border-radius:18px;color:var(--text-color1,#fff);cursor:pointer;transition:all 0.2s;font-size:16px;backdrop-filter:blur(10px);}.emoji-btn:hover,.gift-btn:hover{background-color:#4086FF;transform:scale(1.05);}.audience-drawer-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background-color:rgba(0,0,0,0.5);z-index:1000;display:flex;align-items:flex-end;justify-content:center;}.audience-drawer{width:100%;max-width:500px;height:90%;background-color:rgb(31,32,36);border-radius:20px 20px 0 0;display:flex;flex-direction:column;animation:slideUp 0.3s ease-out;}@keyframes slideUp{from{transform:translateY(100%);}to{transform:translateY(0);}}.drawer-header{display:flex;align-items:center;justify-content:space-between;padding:20px;border-bottom:1px solid #333;}.drawer-header h3{margin:0;font-size:18px;font-weight:600;color:var(--text-color1,#fff);}.close-drawer-btn{display:flex;align-items:center;justify-content:center;width:32px;height:32px;background-color:transparent;border:none;border-radius:50%;color:#999;cursor:pointer;transition:all 0.2s;}.close-drawer-btn:hover{background-color:#333;color:var(--text-color1,#fff);}.drawer-content{flex:1;overflow:hidden;position:relative;}.audience-list{height:100%;overflow-y:auto;padding:16px;}.empty-audience{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:#666;text-align:center;padding:40px 20px;}.empty-icon{font-size:48px;margin-bottom:16px;opacity:0.5;}.empty-audience p{font-size:16px;margin:0;}.live-end-overlay{position:absolute;top:0;left:0;right:0;bottom:0;background-color:rgba(31,32,36,0.95);z-index:1000;display:flex;align-items:center;justify-content:center;}.live-end-content{display:flex;flex-direction:column;align-items:center;gap:20px;text-align:center;padding:40px;}.close-icon{position:absolute;top:20px;right:20px;display:flex;align-items:center;justify-content:center;width:40px;height:40px;background-color:rgba(255,255,255,0.1);border-radius:50%;cursor:pointer;color:#fff;transition:background-color 0.2s;}.close-icon:hover{background-color:rgba(255,255,255,0.2);}.end-title{padding-top:60px;padding-bottom:30px;}.end-title span{font-size:24px;font-weight:600;color:var(--text-color1,#fff);}.end-avatar{border:1px solid var(--uikit-color-white-7);}.end-name{font-size:16px;color:var(--text-color1,#fff);}@media screen and (orientation:landscape){.floating-message-list{width:400px;height:100px;}}@media screen and (orientation:portrait){.floating-message-list{width:260px;height:180px;}.bottom-input{padding:0 20px;}}@media (max-width:768px){.top-bar{padding:0 8px;}.top-left{max-width:70%;padding:6px 10px;}.user-name{max-width:80px;}}
</style>


步骤4: 启动项目

npm run dev

自由定制

另外,我们也支持您根据项目需求对观看页面进行 UI 定制能力。主要可供定制的能力如下表所示。可供定制内容包括但不限于对源码中的颜色主题、字体、圆角、按钮、图标、输入框、弹框等内容,都可以进行增加、删除、修改等定制操作,下列分别给出颜色主题、按钮以及图标的定制示例,您可以参考实现方式通用到其他组件,满足您的 UI 定制需要。
类别
功能
描述
直播信息展示
自定义观看页面信息区域展示
支持:
展示/隐藏 Logo,替换为您需要的 Logo
UI 定制、展示/隐藏关注按钮,替换为您需要的按钮风格
在线观众
自定义观众信息展示
支持:
展示/隐藏观众等级
观众信息字体、颜色 UI 自定义设置
替换为您需要的 Icon 风格
消息列表
自定义消息弹幕区域展示
支持:
展示/隐藏聊天输入区域
支持 UI 定制聊天气泡风格、定制观众等级等内容

颜色主题

参见步骤3中代码示例,您可以使用操作 theme 的值来满足您切换颜色主题。
<UIKitProvider theme="dark"> // theme 传入 dark 时,界面整体颜色主题为黑色主题
xxx // theme 传入 light 时,界面整体颜色主题为白色主题
</UIKitProvider>

按钮 Button / 图标 Icon

若您需要对按钮 Button 或者图标 Icon 进行新增或替换等 UI 定制,您可以通过如下方式进行实现,以观看页面操作的 Icon 为例。参考下图,您可以找到对应 Button / Icon 指定位置源码,对当前部分的按钮进行增加、删除、替换等 UI 定制操作。


播放直播流

步骤1:指定 LiveId 播放

若您需要指定 LiveId 进行播放,您需要在上述快速接入中的步骤3代码基础上增量添加如下内容,参考方式如下:
// live-player.vue
import { onMounted } from 'vue';
import { useLiveState } from 'tuikit-atomicx-vue3';

const { joinLive } = useLiveState();

onMounted(async () => {
await joinLive({ liveId: 'xxx' }); // 输入 liveId 加入直播间,具体参数含义可参考步骤 1 中的准备工作
});

步骤2:路由配置

由于涉及到直播列表(或首页)到直播间的跳转逻辑,您需要配置 Vue Router。在项目 src 目录下新建 router 文件夹,并创建 index.ts 文件。然后,在您的主文件(例如 main.tsindex.ts)中引入并使用路由。可参考 GitHub 代码示例, 如需要直播列表,可参考文档 直播列表
// src/router/index.ts
import { createRouter, createWebHistory } from 'vue-router';

const routes = [
{
path: '/live-player',
component: () => import('../views/live-player.vue'),
},
// 如果需要直播列表功能,可添加如下路由
// {
// path: '/live-list',
// component: () => import('../views/live-list.vue'),
// },
];

const router = createRouter({
history: createWebHistory(),
routes,
});
export default router;

// src/main.ts
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';

const app = createApp(App);
app.use(router);
app.mount('#app');

资源参考

其他参考文档

更多资源