Page:
PHASE2_INTEGRATION_PLAN
Pages
AUTHOR_MODULE
BUGS
BUILD
BUILD_AND_RUN
CHANGELOG
CHOCO_INSTALL
COMPARE_FULLSCREEN
COMPLETION_SUMMARY
CONVERT_MODULARIZATION_PLAN
CROSS_PLATFORM_GUIDE
DEV14_WINDOWS_IMPLEMENTATION
DEV30_FINALIZATION_CHECKLIST
DVD_IMPLEMENTATION_SUMMARY
DVD_USER_GUIDE
Documentation
GNOME_COMPATIBILITY
GSTREAMER_MIGRATION_PLAN
Home
IMPLEMENTATION_SUMMARY
INSTALLATION
INSTALL_LINUX
INSTALL_WINDOWS
INTEGRATION_GUIDE
INTERNAL_DVD_AUTHORING_SPEC
LATEST_UPDATES
LATEX_PREPARATION
LOSSLESSCUT_INSPIRATION
MODULES
PERSISTENT_VIDEO_CONTEXT
PHASE2_COMPLETE
PHASE2_INTEGRATION_PLAN
PLAYER_MODULE
PLAYER_PERFORMANCE_ISSUES
PROJECT_STATUS
QUEUE_SYSTEM_GUIDE
QUICKSTART
REFACTOR_DEV30_PLAN
ROADMAP
TESTING_CHECKLIST
TESTING_DEV13
TESTING_MODULE_CHECKLIST
TEST_DVD_CONVERSION
TODO_EXTRACTION_NOTES
TRIM_MODULE_DESIGN
VIDEO_METADATA_GUIDE
VIDEO_PLAYER
VIDEO_PLAYER_FORK
VT_PLAYER_IMPLEMENTATION
VT_PLAYER_INTEGRATION_NOTES
WINDOWS_BUILD_PERFORMANCE
WINDOWS_COMPATIBILITY
WINDOWS_PACKAGING
WINDOWS_SETUP
WORKING_ON
localization policy
No results
2
PHASE2_INTEGRATION_PLAN
Gemini CLI edited this page 2026-03-13 11:03:09 -04:00
Table of Contents
- Phase 2: GStreamer Integration Plan
- Current State Analysis
- Integration Strategy
- Implementation Steps
- Step 1: Create GStreamer-Based Controller
- Step 2: Update playSession to Use GStreamer
- Step 3: Update playSession Methods
- Step 4: Connect GStreamer Frames to Fyne UI
- Module Integration Points
- Build Order
- Testing Checklist
- Rollback If Needed
- Success Criteria
Phase 2: GStreamer Integration Plan
Current State Analysis
Two Player Systems Found:
-
Player Module (
main.go:6609)- Uses:
player.Controllerinterface - Implementation:
ffplayController(uses external ffplay window) - File:
internal/player/controller_linux.go - Problem: External window, not embedded in Fyne UI
- Uses:
-
Convert Preview (
main.go:11132)- Uses:
playSessionstruct - Implementation:
UnifiedPlayerAdapter(broken FFmpeg pipes) - File: Defined in
main.go - Problem: Uses the UnifiedPlayer we're deleting
- Uses:
Integration Strategy
Option A: Unified Approach (RECOMMENDED)
Replace both systems with a single GStreamer-based player:
GStreamerPlayer (internal/player/gstreamer_player.go)
↓
├──> Player Module (embedded playback)
└──> Convert Preview (embedded preview)
Benefits:
- Single code path
- Easier to maintain
- Both use same solid GStreamer backend
Option B: Hybrid Approach
Keep Controller interface, but make it use GStreamer internally:
Controller interface
↓
GStreamerController (wraps GStreamerPlayer)
↓
GStreamerPlayer
Benefits:
- Minimal changes to main.go
- Controller interface stays the same
We'll use Option A - cleaner, simpler.
Implementation Steps
Step 1: Create GStreamer-Based Controller
File: internal/player/controller_gstreamer.go
//go:build gstreamer
package player
import (
"fmt"
"time"
)
func newController() Controller {
return &gstreamerController{
player: NewGStreamerPlayer(Config{
PreviewMode: false,
WindowWidth: 640,
WindowHeight: 360,
}),
}
}
type gstreamerController struct {
player *GStreamerPlayer
}
func (c *gstreamerController) Load(path string, offset float64) error {
return c.player.Load(path, time.Duration(offset*float64(time.Second)))
}
func (c *gstreamerController) SetWindow(x, y, w, h int) {
c.player.SetWindow(x, y, w, h)
}
func (c *gstreamerController) Play() error {
return c.player.Play()
}
func (c *gstreamerController) Pause() error {
return c.player.Pause()
}
func (c *gstreamerController) Seek(offset float64) error {
return c.player.SeekToTime(time.Duration(offset * float64(time.Second)))
}
func (c *gstreamerController) SetVolume(level float64) error {
// Controller uses 0-100, GStreamer uses 0.0-1.0
return c.player.SetVolume(level / 100.0)
}
func (c *gstreamerController) FullScreen() error {
return c.player.SetFullScreen(true)
}
func (c *gstreamerController) Stop() error {
return c.player.Stop()
}
func (c *gstreamerController) Close() {
c.player.Close()
}
Step 2: Update playSession to Use GStreamer
File: main.go (around line 11132)
BEFORE:
type playSession struct {
// ...
unifiedAdapter *player.UnifiedPlayerAdapter
}
func newPlaySession(...) *playSession {
unifiedAdapter := player.NewUnifiedPlayerAdapter(...)
return &playSession{
unifiedAdapter: unifiedAdapter,
// ...
}
}
AFTER:
type playSession struct {
// ...
gstPlayer *player.GStreamerPlayer
}
func newPlaySession(...) *playSession {
gstPlayer, err := player.NewGStreamerPlayer(player.Config{
PreviewMode: true,
WindowWidth: targetW,
WindowHeight: targetH,
Volume: 1.0,
})
if err != nil {
// Handle error
}
return &playSession{
gstPlayer: gstPlayer,
// ...
}
}
Step 3: Update playSession Methods
Replace all unifiedAdapter calls with gstPlayer:
func (p *playSession) Play() {
p.mu.Lock()
defer p.mu.Unlock()
if p.gstPlayer != nil {
p.gstPlayer.Play()
}
p.paused = false
}
func (p *playSession) Pause() {
p.mu.Lock()
defer p.mu.Unlock()
if p.gstPlayer != nil {
p.gstPlayer.Pause()
}
p.paused = true
}
func (p *playSession) Seek(offset float64) {
p.mu.Lock()
defer p.mu.Unlock()
if p.gstPlayer != nil {
p.gstPlayer.SeekToTime(time.Duration(offset * float64(time.Second)))
}
p.current = offset
// ...
}
func (p *playSession) Stop() {
p.mu.Lock()
defer p.mu.Unlock()
if p.gstPlayer != nil {
p.gstPlayer.Stop()
}
p.stopLocked()
}
Step 4: Connect GStreamer Frames to Fyne UI
The key challenge: GStreamer produces RGBA frames, Fyne needs to display them.
In playSession:
// Start frame display loop
go func() {
ticker := time.NewTicker(time.Second / time.Duration(fps))
defer ticker.Stop()
for {
select {
case <-p.stop:
return
case <-ticker.C:
if p.gstPlayer != nil {
frame, err := p.gstPlayer.GetFrameImage()
if err == nil && frame != nil {
fyne.CurrentApp().Driver().DoFromGoroutine(func() {
p.img.Image = frame
p.img.Refresh()
}, false)
}
}
}
}
}()
Module Integration Points
Modules Using Player:
| Module | Usage | Status | Notes |
|---|---|---|---|
| Player | Main playback | Ready | Uses Controller interface |
| Convert | Preview pane | Ready | Uses playSession |
| Trim | Not implemented | Waiting | Blocked by player |
| Filters | Not implemented | Waiting | Blocked by player |
After GStreamer Integration:
- Player module: Works with GStreamerController
- Convert preview: Works with GStreamerPlayer directly
- Trim module: Can be implemented (player stable)
- Filters module: Can be implemented (player stable)
Build Order
- Install GStreamer (user runs command)
- Create
controller_gstreamer.go - Update
playSessioninmain.go - Build with
./scripts/linux/build.sh - Test Player module
- Test Convert preview
- Verify no crashes
Testing Checklist
Player Module Tests:
- Load video file
- Play button works
- Pause button works
- Seek bar works
- Volume control works
- Frame stepping works (if implemented)
Convert Preview Tests:
- Load video in Convert module
- Preview pane shows video
- Playback works in preview
- Seek works in preview
- Preview updates when converting
Rollback If Needed
If GStreamer integration has issues:
# Revert controller
git checkout HEAD -- internal/player/controller_gstreamer.go
# Revert playSession changes
git checkout HEAD -- main.go
# Rebuild without GStreamer
GOFLAGS="" ./scripts/linux/build.sh
Success Criteria
Phase 2 is complete when:
- GStreamer installed on system
- VideoTools builds with
-tags gstreamer - Player module loads and plays videos
- Convert preview shows video frames
- No crashes during basic playback
- Both systems use GStreamerPlayer backend
Estimated Time: 1-2 hours (mostly testing)
Navigation
What is VideoTools?
Project Status
Capabilities
Codecs and Frame Rates
Installation (One Command)
Alternative: Developer Setup
DVD Workflow (Optional)
Documentation
- Project Status
- Installation
- Readme
- Build And Run
- DVD User Guide
- DVD Implementation Summary
- Integration Guide
- Queue System Guide
- Localization-Policy