Reproductor chewi integrado, wip tags, y diseño

This commit is contained in:
Abraham
2026-01-12 17:07:41 -08:00
parent 854a0940ae
commit a9214e9eac
8 changed files with 680 additions and 40 deletions

View File

@@ -0,0 +1,75 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:appinio_video_player/appinio_video_player.dart';
class VideoScreenNew extends StatefulWidget {
final dynamic videoUrl;
const VideoScreenNew({Key? key, required this.videoUrl}) : super(key: key);
@override
_VideoScreenNewState createState() => _VideoScreenNewState();
}
class _VideoScreenNewState extends State<VideoScreenNew> {
late VideoPlayerController _videoPlayerController;
late CustomVideoPlayerController _customVideoPlayerController;
late CustomVideoPlayerWebController _customVideoPlayerWebController;
final CustomVideoPlayerSettings _customVideoPlayerSettings =
const CustomVideoPlayerSettings();
late CustomVideoPlayerWebSettings _customVideoPlayerWebSettings;
late VideoPlayerController _controller;
@override
void initState() {
super.initState();
_videoPlayerController = VideoPlayerController.network(
widget.videoUrl,
)..initialize().then((value) => setState(() {}));
_customVideoPlayerController = CustomVideoPlayerController(
context: context,
videoPlayerController: _videoPlayerController,
customVideoPlayerSettings: _customVideoPlayerSettings,
);
_customVideoPlayerWebSettings = CustomVideoPlayerWebSettings(
src: widget.videoUrl,
);
_customVideoPlayerWebController = CustomVideoPlayerWebController(
webVideoPlayerSettings: _customVideoPlayerWebSettings,
);
_controller = VideoPlayerController.network(widget.videoUrl)
..initialize().then((_) {
setState(() {});
});
}
@override
void dispose() {
_customVideoPlayerController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
backgroundColor: Colors.black,
child: SafeArea(
child: Center(
child: AspectRatio(
aspectRatio: _controller.value.aspectRatio,
child: CustomVideoPlayerWeb(
customVideoPlayerWebController: _customVideoPlayerWebController,
),
),
),
),
);
}
}

View File

@@ -0,0 +1,80 @@
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
import 'package:chewie/chewie.dart';
class VideoPlayerLive extends StatefulWidget {
final String url;
const VideoPlayerLive({Key? key, required this.url}) : super(key: key);
@override
_VideoPlayerLiveState createState() => _VideoPlayerLiveState();
}
class _VideoPlayerLiveState extends State<VideoPlayerLive> {
late ChewieController _chewieController;
bool _isFullScreen = false;
@override
void initState() {
super.initState();
_initializePlayer();
}
@override
void dispose() {
super.dispose();
_chewieController.dispose();
}
void _initializePlayer() {
final videoPlayerController = VideoPlayerController.network(widget.url);
_chewieController = ChewieController(
videoPlayerController: videoPlayerController,
autoPlay: false,
looping: false,
showControls: true,
allowFullScreen: true,
allowMuting: true,
allowPlaybackSpeedChanging: false,
aspectRatio: videoPlayerController.value.aspectRatio,
customControls: CupertinoControls(
backgroundColor: Color.fromARGB(66, 0, 0, 0),
iconColor: Color.fromARGB(255, 202, 202, 202),
showPlayButton: true),
);
}
void _toggleFullScreen() {
if (!_isFullScreen) {
_chewieController.enterFullScreen();
} else {
_chewieController.exitFullScreen();
}
setState(() {
_isFullScreen = !_isFullScreen;
});
}
@override
Widget build(BuildContext context) {
return Container(
color: Colors.black,
child: Center(
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: _toggleFullScreen,
child: AspectRatio(
aspectRatio:
_chewieController.videoPlayerController.value.aspectRatio,
child: Chewie(
controller: _chewieController,
),
),
),
),
),
);
}
}

View File

@@ -0,0 +1,69 @@
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
class VideoScreenThumbnail extends StatefulWidget {
final dynamic video;
const VideoScreenThumbnail({Key? key, required this.video}) : super(key: key);
@override
State<VideoScreenThumbnail> createState() => _VideoScreenThumbnailState();
}
class _VideoScreenThumbnailState extends State<VideoScreenThumbnail> {
late VideoPlayerController _controllerVideo;
late bool startedPlaying;
@override
void initState() {
_controllerVideo = VideoPlayerController.network(widget.video);
_controllerVideo.initialize();
super.initState();
started();
_controllerVideo.setVolume(0);
_controllerVideo.pause();
}
Future<bool> started() async {
var renderized = false;
double height = 0;
await Future.delayed(const Duration(seconds: 2), () {
height = _controllerVideo.value.size.height;
if (height > 0) renderized = true;
});
return renderized;
}
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: started(),
builder: ((context, AsyncSnapshot<bool> snapshot) {
if (snapshot.data ?? false) {
return Stack(
alignment: AlignmentDirectional.center,
children: [
VideoPlayer(_controllerVideo),
Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
SizedBox(height: 30),
],
),
],
);
} else {
return const Center(child: CircularProgressIndicator());
}
}),
);
}
@override
void dispose() {
super.dispose();
var video = _controllerVideo;
video.dispose();
}
}

View File

@@ -7,6 +7,7 @@ import 'package:nethive_neo/theme/theme.dart';
import 'package:nethive_neo/helpers/globals.dart';
import 'package:nethive_neo/widgets/premium_button.dart';
import 'package:nethive_neo/pages/videos/widgets/premium_upload_dialog.dart';
import 'package:nethive_neo/pages/videos/widgets/video_player_dialog.dart';
import 'package:gap/gap.dart';
class GestorVideosPage extends StatefulWidget {
@@ -582,10 +583,31 @@ class _GestorVideosPageState extends State<GestorVideosPage> {
}
void _playVideo(MediaFileModel video) {
// TODO: Implementar reproductor de video
// Verificar que el video tenga URL
if (video.fileUrl == null || video.fileUrl!.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Reproduciendo: ${video.title ?? video.fileName}'),
const SnackBar(
content: Text('Error: El video no tiene URL válida'),
backgroundColor: Color(0xFFFF2D2D),
),
);
return;
}
// Abrir diálogo con reproductor
showDialog(
context: context,
barrierDismissible: true,
builder: (context) => VideoPlayerDialog(
video: video,
onPlaybackCompleted: () {
// Incrementar reproducciones cuando termine el video
final provider = Provider.of<VideosProvider>(context, listen: false);
provider.incrementReproduccion(video.mediaFileId);
},
onClose: () {
// Opcional: realizar alguna acción al cerrar
},
),
);
}

View File

@@ -0,0 +1,358 @@
import 'package:flutter/material.dart';
import 'package:chewie/chewie.dart';
import 'package:video_player/video_player.dart';
import 'package:nethive_neo/theme/theme.dart';
import 'package:nethive_neo/models/media/media_models.dart';
import 'package:gap/gap.dart';
class VideoPlayerDialog extends StatefulWidget {
final MediaFileModel video;
final VoidCallback? onPlaybackCompleted;
final VoidCallback? onClose;
const VideoPlayerDialog({
Key? key,
required this.video,
this.onPlaybackCompleted,
this.onClose,
}) : super(key: key);
@override
State<VideoPlayerDialog> createState() => _VideoPlayerDialogState();
}
class _VideoPlayerDialogState extends State<VideoPlayerDialog> {
late VideoPlayerController _videoPlayerController;
ChewieController? _chewieController;
bool _isLoading = true;
String? _errorMessage;
@override
void initState() {
super.initState();
_initializePlayer();
}
Future<void> _initializePlayer() async {
try {
// Inicializar video player con URL del video
_videoPlayerController = VideoPlayerController.network(
widget.video.fileUrl!,
);
await _videoPlayerController.initialize();
// Configurar Chewie (controles de video)
_chewieController = ChewieController(
videoPlayerController: _videoPlayerController,
autoPlay: true,
looping: false,
showControls: true,
materialProgressColors: ChewieProgressColors(
playedColor: const Color(0xFF4EC9F5),
handleColor: const Color(0xFFFFB733),
backgroundColor: Colors.grey.shade800,
bufferedColor: Colors.grey.shade600,
),
autoInitialize: true,
allowFullScreen: true,
allowMuting: true,
showControlsOnInitialize: true,
errorBuilder: (context, errorMessage) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.error_outline,
color: Color(0xFFFF2D2D),
size: 60,
),
const Gap(16),
Text(
'Error al cargar el video',
style: TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const Gap(8),
Text(
errorMessage,
style: TextStyle(
color: Colors.white70,
fontSize: 14,
),
textAlign: TextAlign.center,
),
],
),
);
},
);
// Listener para detectar fin del video
_videoPlayerController.addListener(() {
if (_videoPlayerController.value.position ==
_videoPlayerController.value.duration) {
widget.onPlaybackCompleted?.call();
}
});
setState(() {
_isLoading = false;
});
} catch (e) {
setState(() {
_isLoading = false;
_errorMessage = 'Error al cargar el video: $e';
});
print('Error inicializando video player: $e');
}
}
@override
void dispose() {
_videoPlayerController.dispose();
_chewieController?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final screenSize = MediaQuery.of(context).size;
final isMobile = screenSize.width <= 800;
return Dialog(
backgroundColor: Colors.transparent,
insetPadding: EdgeInsets.all(isMobile ? 8 : 40),
child: Container(
width: isMobile ? screenSize.width : screenSize.width * 0.8,
constraints: BoxConstraints(
maxWidth: 1200,
maxHeight: screenSize.height * 0.9,
),
decoration: BoxDecoration(
color: AppTheme.of(context).secondaryBackground,
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: AppTheme.of(context).primaryColor.withOpacity(0.3),
width: 2,
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.5),
blurRadius: 20,
spreadRadius: 5,
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Header
_buildHeader(context, isMobile),
// Video Player
Flexible(
child: Container(
width: double.infinity,
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(16),
bottomRight: Radius.circular(16),
),
),
child: _buildVideoPlayer(context),
),
),
],
),
),
);
}
Widget _buildHeader(BuildContext context, bool isMobile) {
return Container(
padding: EdgeInsets.all(isMobile ? 12 : 16),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
AppTheme.of(context).primaryBackground,
AppTheme.of(context).secondaryBackground,
],
),
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
),
border: Border(
bottom: BorderSide(
color: AppTheme.of(context).primaryColor.withOpacity(0.2),
width: 1,
),
),
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [
Color(0xFF4EC9F5),
Color(0xFFFFB733),
],
),
borderRadius: BorderRadius.circular(8),
),
child: const Icon(
Icons.play_circle_filled,
color: Color(0xFF0B0B0D),
size: 20,
),
),
const Gap(12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.video.title ?? widget.video.fileName,
style: AppTheme.of(context).bodyText1.override(
fontFamily: 'Poppins',
color: AppTheme.of(context).primaryText,
fontWeight: FontWeight.bold,
fontSize: isMobile ? 14 : 16,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
if (widget.video.fileDescription != null &&
widget.video.fileDescription!.isNotEmpty &&
!isMobile)
Text(
widget.video.fileDescription!,
style: AppTheme.of(context).bodyText2.override(
fontFamily: 'Poppins',
color: AppTheme.of(context).tertiaryText,
fontSize: 12,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
IconButton(
icon: Icon(
Icons.close,
color: AppTheme.of(context).primaryText,
),
onPressed: () {
widget.onClose?.call();
Navigator.of(context).pop();
},
tooltip: 'Cerrar',
),
],
),
);
}
Widget _buildVideoPlayer(BuildContext context) {
if (_isLoading) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(
color: AppTheme.of(context).primaryColor,
),
const Gap(16),
Text(
'Cargando video...',
style: TextStyle(
color: Colors.white,
fontSize: 16,
),
),
],
),
);
}
if (_errorMessage != null) {
return Center(
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.error_outline,
color: Color(0xFFFF2D2D),
size: 60,
),
const Gap(16),
Text(
'Error al cargar el video',
style: TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const Gap(8),
Text(
_errorMessage!,
style: TextStyle(
color: Colors.white70,
fontSize: 14,
),
textAlign: TextAlign.center,
),
const Gap(24),
ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFFF2D2D),
foregroundColor: Colors.white,
),
child: const Text('Cerrar'),
),
],
),
),
);
}
if (_chewieController == null) {
return Center(
child: Text(
'No se pudo inicializar el reproductor',
style: TextStyle(
color: Colors.white,
fontSize: 16,
),
),
);
}
return ClipRRect(
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(16),
bottomRight: Radius.circular(16),
),
child: Chewie(
controller: _chewieController!,
),
);
}
}

View File

@@ -10,11 +10,13 @@ import file_picker
import file_selector_macos
import flutter_secure_storage_macos
import flutter_tts
import package_info_plus
import path_provider_foundation
import shared_preferences_foundation
import sign_in_with_apple
import url_launcher_macos
import wakelock_macos
import video_player_avfoundation
import wakelock_plus
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin"))
@@ -22,9 +24,11 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
FlutterTtsPlugin.register(with: registry.registrar(forPlugin: "FlutterTtsPlugin"))
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
SignInWithApplePlugin.register(with: registry.registrar(forPlugin: "SignInWithApplePlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
WakelockMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockMacosPlugin"))
FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin"))
WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin"))
}

View File

@@ -33,6 +33,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.5.1"
args:
dependency: transitive
description:
name: args
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
url: "https://pub.dev"
source: hosted
version: "2.7.0"
async:
dependency: transitive
description:
@@ -101,10 +109,10 @@ packages:
dependency: "direct main"
description:
name: chewie
sha256: "3bc4fbae99a9f39dc9c04ba70ad2a2fb902dbd3d3e36ad8ab3a53f0dcbf311be"
sha256: "4d9554a8f87cc2dc6575dfd5ad20a4375015a29edd567fd6733febe6365e2566"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
version: "1.11.3"
chip_list:
dependency: "direct main"
description:
@@ -201,6 +209,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.17.0"
dbus:
dependency: transitive
description:
name: dbus
sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c"
url: "https://pub.dev"
source: hosted
version: "0.7.11"
dio:
dependency: "direct main"
description:
@@ -853,6 +869,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.1.1"
package_info_plus:
dependency: transitive
description:
name: package_info_plus
sha256: "16eee997588c60225bda0488b6dcfac69280a6b7a3cf02c741895dd370a02968"
url: "https://pub.dev"
source: hosted
version: "8.3.1"
package_info_plus_platform_interface:
dependency: transitive
description:
name: package_info_plus_platform_interface
sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086"
url: "https://pub.dev"
source: hosted
version: "3.2.1"
path:
dependency: "direct main"
description:
@@ -917,6 +949,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.2.5"
petitparser:
dependency: transitive
description:
name: petitparser
sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1"
url: "https://pub.dev"
source: hosted
version: "7.0.1"
platform:
dependency: transitive
description:
@@ -1350,26 +1390,26 @@ packages:
dependency: "direct main"
description:
name: video_player
sha256: "868a139229acb5018d22aded3eb9cb4767ff43a8216573c086b6c535a4957481"
sha256: "096bc28ce10d131be80dfb00c223024eb0fba301315a406728ab43dd99c45bdf"
url: "https://pub.dev"
source: hosted
version: "2.6.0"
version: "2.10.1"
video_player_android:
dependency: transitive
description:
name: video_player_android
sha256: e7de6fabe5d96048cd8f4d710f25c3df84bb3cab8b22da6c082bd8f39e316984
sha256: a8dc4324f67705de057678372bedb66cd08572fe7c495605ac68c5f503324a39
url: "https://pub.dev"
source: hosted
version: "2.4.3"
version: "2.8.15"
video_player_avfoundation:
dependency: transitive
description:
name: video_player_avfoundation
sha256: "90468226c8687adf7b567d9bb42c25588783c4d30509af1fbd663b2dd049f700"
sha256: f9a780aac57802b2892f93787e5ea53b5f43cc57dc107bee9436458365be71cd
url: "https://pub.dev"
source: hosted
version: "2.4.2"
version: "2.8.4"
video_player_platform_interface:
dependency: transitive
description:
@@ -1394,38 +1434,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "15.0.0"
wakelock:
wakelock_plus:
dependency: transitive
description:
name: wakelock
sha256: "78bad4822be81d37e7bc34b6990da7dface2a445255cd37c6f053b51a4ccdb3b"
name: wakelock_plus
sha256: "775c50f226ab43ff859b479acc73f11c0744bf345a782e83355c4d25df758803"
url: "https://pub.dev"
source: hosted
version: "0.4.0"
wakelock_macos:
version: "1.3.0"
wakelock_plus_platform_interface:
dependency: transitive
description:
name: wakelock_macos
sha256: "73581e5d9ed2dd1ba951375c30e63f0eb8c58d7d6286ae9ddf927b88f2aea8d9"
name: wakelock_plus_platform_interface
sha256: "036deb14cd62f558ca3b73006d52ce049fabcdcb2eddfe0bf0fe4e8a943b5cf2"
url: "https://pub.dev"
source: hosted
version: "0.1.0+3"
wakelock_platform_interface:
dependency: transitive
description:
name: wakelock_platform_interface
sha256: d0a8a1c02af68077db5df1e0f5e2b745f7b1f2cdcc48e3e0b6f8f4dcc349050e
url: "https://pub.dev"
source: hosted
version: "0.2.1+3"
wakelock_web:
dependency: transitive
description:
name: wakelock_web
sha256: "06b0033d5421712138e7fa482ff5c6280fe12e0a41c40c3fe8fda2c007eb4348"
url: "https://pub.dev"
source: hosted
version: "0.2.0+3"
version: "1.3.0"
web:
dependency: transitive
description:
@@ -1498,6 +1522,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.4"
xml:
dependency: transitive
description:
name: xml
sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025"
url: "https://pub.dev"
source: hosted
version: "6.6.1"
yet_another_json_isolate:
dependency: transitive
description:
@@ -1507,5 +1539,5 @@ packages:
source: hosted
version: "1.1.1"
sdks:
dart: ">=3.7.0 <4.0.0"
dart: ">=3.8.0 <4.0.0"
flutter: ">=3.29.0"

View File

@@ -74,7 +74,7 @@ dependencies:
flutter_animate: ^4.2.0
flutter_flow_chart: 3.2.3
video_player: ^2.6.0
chewie: ^1.0.0
chewie: ^1.8.0
shimmer: ^3.0.0
fl_chart: 0.69.0