egui_sharkplayer/lib.rs
1//! A hardware-accelerated video widget for `egui`, backed by [`libmpv2`].
2//!
3//! This crate provides an immediate-mode video player for `eframe`
4//! applications.
5//!
6//! # Overview
7//!
8//! - [`PlayerState`]: Manages the `libmpv` context, media properties, and
9//! playback state. This requires access to the graphics context and must be
10//! initialized once using the [`eframe::CreationContext`].
11//!
12//! A single [`PlayerState`] maps to a single video stream. Displaying
13//! multiple videos simultaneously requires independent state instances.
14//!
15//! - [`SharkPlayer`]: The actual UI widget. This is what you create in your
16//! `ui` method.
17//!
18//! The video player works best with the `glow` backend because libmpv2's render
19//! API only supports OpenGL. When using the `wgpu` feature, instead of a direct
20//! VRAM copy, image data has to roundtrip through main system memory. If at all
21//! possible, use `glow` and disable the `wgpu` feature.
22//!
23//! Because playback is driven with mpv, and video frames are rendered straight
24//! into an OpenGL framebuffer, this only works when using `eframe`'s `glow`
25//! rendering backend; `wgpu` is not supported.
26//!
27//! # Features
28//!
29//! - Integrated contol overlays (play/pause, volume, seekbar, info overlay,
30//! etc.)
31//! - Fullscreening support
32//!
33//! # Quick Start
34//!
35//! ```no_run
36//! use eframe::egui;
37//! use egui_sharkplayer::{PlayerState, SharkPlayer};
38//!
39//! struct App {
40//! player: PlayerState,
41//! }
42//!
43//! impl App {
44//! fn new(cc: &eframe::CreationContext<'_>) -> Self {
45//! let player = PlayerState::new(cc).expect("failed to initialize player");
46//! player.load_file("video.mp4").expect("failed to load video.mp4");
47//! Self { player }
48//! }
49//! }
50//!
51//! impl eframe::App for App {
52//! fn on_exit(&mut self, _gl: Option<&eframe::glow::Context>) {
53//! // Required: see "Cleaning up".
54//! self.player.destroy_gl_resources();
55//! }
56//!
57//! fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
58//! egui::CentralPanel::default().show_inside(ui, |ui| {
59//! ui.add(SharkPlayer::new(&mut self.player));
60//! });
61//! }
62//! }
63//!
64//! fn main() {
65//! eframe::run_native(
66//! "Sharkplayer",
67//! eframe::NativeOptions::default(),
68//! Box::new(|cc| Ok(Box::new(App::new(cc)))),
69//! )
70//! .unwrap();
71//! }
72//! ```
73//!
74//! If you want to play multiple videos at the same time, you'll need to have
75//! multiple [`PlayerState`]s.
76//!
77//! # Player Size
78//!
79//! By default, the widget claims all the space available in its panel. However,
80//! it's possible to contrain one of the axes using [`SharkPlayer::sizing`] to
81//! pin one axis instead — the other is derived from the video's aspect ratio,
82//! so the player doesn't letterbox.
83//!
84//! ```no_run
85//! # use eframe::egui;
86//! # use egui_sharkplayer::{PlayerState, SharkPlayer, Sizing};
87//! # fn ui(ui: &mut egui::Ui, player: &mut PlayerState) {
88//! ui.add(SharkPlayer::new(player).sizing(Sizing::Width(640.0)));
89//! # }
90//! ```
91//!
92//! # Styling Controls
93//!
94//! For the most part, the controls use standard egui styling, but some
95//! components have special styling options.
96//!
97//! ## Bar Height
98//! The player bar has a default height, but it can be overridden with
99//! [`SharkPlayer::bar_height`].
100//!
101//! ```no_run
102//! # use eframe::egui;
103//! # use egui_sharkplayer::{PlayerState, SharkPlayer, Sizing};
104//! # fn ui(ui: &mut egui::Ui, player: &mut PlayerState) {
105//! ui.add(
106//! SharkPlayer::new(player)
107//! .sizing(Sizing::Height(360.0))
108//! .bar_height(32.0),
109//! );
110//! # }
111//! ```
112//!
113//! ## Custom icons
114//!
115//! To customize which icons are used for the controls, you need a
116//! [`ControlsIconProvider`].
117//!
118//! ```no_run
119//! # use eframe::egui;
120//! use egui_sharkplayer::{ControlsIconProvider, PlayerState, SharkPlayer};
121//!
122//! struct Icons;
123//!
124//! impl ControlsIconProvider for Icons {
125//! fn play(&self) -> egui::WidgetText { "Play".into() }
126//! fn pause(&self) -> egui::WidgetText { "Pause".into() }
127//! }
128//!
129//! # fn ui(ui: &mut egui::Ui, player: &mut PlayerState) {
130//! ui.add(SharkPlayer::new_with_icons(player, Icons));
131//! # }
132//! ```
133//!
134//!
135//! # Cleaning Up
136//!
137//! To clean up the player's OpenGL resources, call
138//! [`PlayerState::destroy_gl_resources`] from [`eframe::App::on_exit`].
139//!
140//! This isn't strictly necessary it's fine to leak VRAM, like if the process
141//! stops when the UI closes.
142
143mod backend;
144mod widget;
145
146pub use backend::{BackendError, PlayerState};
147pub use widget::{ControlsIconProvider, DefaultControlsIconProvider, Keybinds, SharkPlayer, Sizing};