Files
vova 64a337ffe8 Improve LibVLC stability and add external playback host
- Add LibVLCManager singleton for safer LibVLC lifetime management
- Introduce VlcHostClient and VlcHost.exe for external playback via JSON commands
- Enhance VideoPlayer with error recovery and dependency property for SourceUrl
- Implement IDisposable in ProgramsData and ViewModels for better cleanup
- Update NuGet packages: LibVLCSharp to 3.9.6, System.Reactive to 6.1.0
- Add robust error handling and resource disposal throughout
- Improve program guide handling in PlayerViewModel
2026-04-10 14:29:00 +03:00

224 lines
7.7 KiB
C#

using CommunityToolkit.Mvvm.Input;
using System;
using System.Reactive.Linq;
using System.Windows.Input;
using TV_Player.ViewModels;
namespace TV_Player
{
public class PlayerViewModel : ObservableViewModelBase, IDisposable
{
public delegate void SourceUrlChanged(string videoURL);
public event SourceUrlChanged SourceUrlChangedEvent;
private M3UInfo _currentProgram;
private bool _isProgramInfoVisible;
public bool IsProgramInfoVisible
{
get => _isProgramInfoVisible;
set => SetProperty(ref _isProgramInfoVisible, value);
}
private string _topPaneTitle;
public string TopPanelTitle
{
get => _topPaneTitle;
set => SetProperty(ref _topPaneTitle, value);
}
private int _durationValue;
public int DurationValue
{
get => _durationValue;
set => SetProperty(ref _durationValue, value);
}
private string _programGuide;
public string ProgramGuideText
{
get => _programGuide;
set => SetProperty(ref _programGuide, value);
}
private string _startProgram;
public string StartProgram
{
get => _startProgram;
set => SetProperty(ref _startProgram, value);
}
private string _endProgram;
public string EndProgram
{
get => _endProgram;
set => SetProperty(ref _endProgram, value);
}
private bool _programGuideVisible;
public bool ProgramGuideVisible
{
get => _programGuideVisible;
set => SetProperty(ref _programGuideVisible, value);
}
List<ProgramInfo> _programsOnCurrentChannel;
public List<ProgramInfo> Programs
{
get => _programsOnCurrentChannel;
set => SetProperty(ref _programsOnCurrentChannel, value);
}
private List<M3UInfo> _programs;
public ICommand BackCommand { get; }
public ICommand FullscreenCommand { get; }
public ICommand NextCommand { get; }
public ICommand PreviousCommand { get; }
public ICommand ShowProgramListCommand { get; }
public ICommand CloseAppCommand { get; }
private ProgramGuide _currentGuide;
private ProgramInfo _currentProgramInfo;
private IDisposable _programGuideDisposable;
private IDisposable _timer;
private IDisposable _programSubscriber;
private int _currentProgramIndex = 0;
public PlayerViewModel(M3UInfo selectedProgram)
{
_currentProgram = selectedProgram;
BackCommand = new RelayCommand(OnButtonBackClick);
NextCommand = new RelayCommand(NextProgram);
PreviousCommand = new RelayCommand(PreviousProgram);
FullscreenCommand = new RelayCommand(TVPlayerViewModel.Instance.FullScreenToggle);
CloseAppCommand = new RelayCommand(TVPlayerViewModel.Instance.CloseAppCommand);
ShowProgramListCommand = new RelayCommand(ShowProgramList);
ProgramGuideVisible = false;
_programSubscriber = TVPlayerViewModel.Instance.CurrentProgrmsData.AllPrograms.Subscribe(x =>
{
_programs = x.Where(p => p.GroupTitle == _currentProgram.GroupTitle).ToList();
_currentProgramIndex = _programs.Select((program, index) => new { program, index })
.Where(x => x.program.Name == _currentProgram.Name)
.Select(x => x.index)
.FirstOrDefault();
});
UpdateUI();
}
private void UpdateUI()
{
TVPlayerViewModel.Instance.TopPanelVisible(false, _currentProgram.Name);
TopPanelTitle = _currentProgram.Name;
SourceUrlChangedEvent?.Invoke(_currentProgram.Url);
_programGuideDisposable = TVPlayerViewModel.Instance.CurrentProgrmsData.ProgramGuideInfo.Subscribe(async x =>
{
try
{
_currentGuide = await TVPlayerViewModel.Instance.CurrentProgrmsData.GetGuideByProgram(_currentProgram.TvgID);
UpdateScreenInfo();
_timer = Observable.Interval(TimeSpan.FromMinutes(1)).Subscribe(x =>
{
UpdateScreenInfo();
});
}
catch { }
});
}
private void PreviousProgram()
{
_currentProgramIndex -= 1;
if (_currentProgramIndex < 0)
_currentProgramIndex = _programs.Count - 1;
_currentProgram = _programs[_currentProgramIndex];
_programGuideDisposable?.Dispose();
UpdateUI();
}
private void NextProgram()
{
_currentProgramIndex += 1;
if (_currentProgramIndex > _programs.Count - 1)
_currentProgramIndex = 0;
_currentProgram = _programs[_currentProgramIndex];
_programGuideDisposable?.Dispose();
UpdateUI();
}
private void ShowProgramList()
{
ProgramGuideVisible = true;
}
private void UpdateScreenInfo()
{
try
{
if (_currentProgram == null) return;
// make sure guide and programs list exist
if (_currentGuide?.Programs == null || _currentGuide.Programs.Count == 0)
{
IsProgramInfoVisible = false;
Programs = new List<ProgramInfo>();
return;
}
_currentProgramInfo = _currentGuide.Programs.FirstOrDefault(d => d.StartTime <= DateTime.Now && d.EndTime >= DateTime.Now);
if (_currentProgramInfo == null)
{
// no current program found: hide info and show the first N programs as a fallback
IsProgramInfoVisible = false;
Programs = _currentGuide.Programs.Take(7).ToList();
}
else
{
// find the index of the current program safely
int idx = _currentGuide.Programs.FindIndex(x => string.Equals(x.Title, _currentProgramInfo.Title, StringComparison.Ordinal));
if (idx < 0) idx = 0;
Programs = _currentGuide.Programs.Skip(idx).Take(7).ToList();
}
if (_currentProgramInfo == null)
{
IsProgramInfoVisible = false;
}
else if (_currentProgramInfo.Title != ProgramGuideText)
{
IsProgramInfoVisible = true;
ProgramGuideText = _currentProgramInfo.Title;
StartProgram = _currentProgramInfo.StartTime.ToShortTimeString();
EndProgram = _currentProgramInfo.EndTime.ToShortTimeString();
var programMinutes = (_currentProgramInfo.EndTime - _currentProgramInfo.StartTime).TotalMinutes;
DurationValue = (int)((DateTime.Now - _currentProgramInfo.StartTime).TotalMinutes / programMinutes * 100);
}
}
catch
{ }
}
private void OnButtonBackClick()
{
var groupInfo = new GroupInfo() { Name = _currentProgram.GroupTitle, Count = 0 };
TVPlayerViewModel.Instance.ShowProgramsListScreen(groupInfo);
}
public void Dispose()
{
_programSubscriber.Dispose();
_programGuideDisposable.Dispose();
_timer.Dispose();
}
}
}