推流实现
安装web_socket_channel
flutter pub add web_socket_channel
Dart
import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart'; // Flutter官方视频播放插件
import 'package:web_socket_channel/web_socket_channel.dart'; // 模拟实时弹幕
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '直播互动抽奖',
theme: ThemeData.dark(), // 直播间通常使用深色主题
home: const LiveRoomPage(),
);
}
}
class LiveRoomPage extends StatefulWidget {
const LiveRoomPage({super.key});
@override
State<LiveRoomPage> createState() => _LiveRoomPageState();
}
class _LiveRoomPageState extends State<LiveRoomPage> {
// 1. 视频播放控制器
VideoPlayerController? _videoController;
bool _isVideoInitialized = false;
bool _isReconnecting = false;
// 2. 实时评论相关
final TextEditingController _commentController = TextEditingController();
final ScrollController _scrollController = ScrollController();
final List<String> _comments = [];
WebSocketChannel? _channel;
// 3. 抽奖相关
final Set<String> _lotteryPool = {}; // 参与抽奖的用户池(去重)
String? _winner;
int _countdown = 0;
Timer? _countdownTimer;
bool _isDrawing = false;
@override
void initState() {
super.initState();
_initLiveStream();
_connectDanmaku();
}
// --- 1. 直播画面与中断重连逻辑 ---
void _initLiveStream() {
// 这里使用一个公开的测试直播流(HLS格式),实际项目中替换为你的直播推流地址
const testLiveUrl = 'https://bitdash-a.akamaihd.net/content/MI201109210084_1/m3u8s/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.m3u8';
_videoController = VideoPlayerController.networkUrl(Uri.parse(testLiveUrl))
..initialize().then((_) {
setState(() { _isVideoInitialized = true; });
_videoController!.play();
_videoController!.setLooping(true); // 直播流通常设为循环或持续拉流
});
// 监听视频播放状态,实现断线重连
_videoController!.addListener(() {
if (_videoController!.value.hasError && !_isReconnecting) {
print("直播流中断,正在尝试重连...");
_reconnectLiveStream();
}
});
}
void _reconnectLiveStream() {
setState(() { _isReconnecting = true; });
// 延迟3秒后重新初始化视频控制器
Future.delayed(const Duration(seconds: 3), () {
if (mounted) {
_videoController?.dispose();
_initLiveStream();
setState(() { _isReconnecting = false; });
}
});
}
// --- 2. 实时评论与发送 ---
void _connectDanmaku() {
// 模拟连接弹幕服务器(实际项目中这里应替换为 TUILiveKit 的弹幕回调或第三方平台WebSocket)
// 这里仅做UI逻辑演示,真实环境需替换为实际的弹幕接收逻辑
_addComment("系统", "欢迎来到直播间!发送评论即可参与抽奖~");
}
void _addComment(String user, String msg) {
setState(() {
_comments.add('$user: $msg');
_lotteryPool.add(user); // 发送评论自动加入抽奖池
});
// 自动滚动到底部
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_scrollController.hasClients) {
_scrollController.animateTo(
_scrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut,
);
}
});
}
void _sendComment() {
if (_commentController.text.trim().isEmpty) return;
final myComment = _commentController.text;
_addComment("我", myComment); // 实际项目中需调用 SDK 发送弹幕接口
_commentController.clear();
}
// --- 3. 倒计时抽奖功能 ---
void _startLottery() {
if (_lotteryPool.isEmpty || _isDrawing) return;
setState(() {
_isDrawing = true;
_winner = null;
_countdown = 5; // 5秒倒计时
});
// 倒计时逻辑
_countdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (_countdown > 1) {
setState(() { _countdown--; });
} else {
timer.cancel();
_drawWinner(); // 倒计时结束,抽取中奖者
}
});
}
void _drawWinner() {
final random = Random();
final poolList = _lotteryPool.toList();
final luckyUser = poolList[random.nextInt(poolList.length)];
setState(() {
_winner = "🎉 恭喜用户 [$luckyUser] 中奖!";
_isDrawing = false;
});
}
@override
void dispose() {
_videoController?.dispose();
_commentController.dispose();
_scrollController.dispose();
_countdownTimer?.cancel();
_channel?.sink.close();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Stack(
children: [
// 底层:直播画面
Center(
child: _isVideoInitialized
? AspectRatio(
aspectRatio: _videoController!.value.aspectRatio,
child: VideoPlayer(_videoController!),
)
: const CircularProgressIndicator(color: Colors.white),
),
if (_isReconnecting)
const Center(child: Text("直播中断,重连中...", style: TextStyle(color: Colors.red, fontSize: 18))),
// 顶层:UI交互层
Column(
children: [
// 顶部抽奖按钮与结果
Padding(
padding: const EdgeInsets.only(top: 40, left: 16, right: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ElevatedButton(
onPressed: _isDrawing ? null : _startLottery,
style: ElevatedButton.styleFrom(backgroundColor: Colors.orange),
child: Text(_isDrawing ? "倒计时 $_countdown 秒" : "开始抽奖 (当前池子: ${_lotteryPool.length}人)"),
),
if (_winner != null)
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
color: Colors.red.withOpacity(0.8),
child: Text(_winner!, style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
),
],
),
),
const Spacer(), // 将评论区挤到底部
// 底部评论区与输入框
Container(
padding: const EdgeInsets.all(10),
color: Colors.black.withOpacity(0.6),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// 评论列表
SizedBox(
height: 150,
child: ListView.builder(
controller: _scrollController,
itemCount: _comments.length,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Text(_comments[index], style: const TextStyle(color: Colors.white, fontSize: 14)),
);
},
),
),
// 输入框
Row(
children: [
Expanded(
child: TextField(
controller: _commentController,
style: const TextStyle(color: Colors.white),
decoration: const InputDecoration(
hintText: "发送评论参与抽奖...",
hintStyle: TextStyle(color: Colors.white54),
border: OutlineInputBorder(),
),
),
),
IconButton(
icon: const Icon(Icons.send, color: Colors.blue),
onPressed: _sendComment,
),
],
)
],
),
),
],
),
],
),
);
}
}