Archived
1
0
This repository has been archived on 2026-04-22. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
FoxTube/FoxTube/Controls/Player/PlayerControls.cs
T
Michael Gordeev cebbf4fef5 - Updated NuGet packs
- Added quality switcher to livestreams
- Fixed SettingsStorage cast
- Completed ManifestGenerator (but still doesn't work)
- Refactored and redesigned player
- Created test project
2019-05-14 19:34:04 +03:00

518 lines
19 KiB
C#

using FoxTube.Controls;
using FoxTube.Controls.Adverts;
using FoxTube.Controls.Player;
using Google.Apis.YouTube.v3.Data;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Windows.ApplicationModel.Resources;
using Windows.Graphics.Display;
using Windows.Media.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using YoutubeExplode;
using YoutubeExplode.Models.ClosedCaptions;
using YoutubeExplode.Models.MediaStreams;
namespace FoxTube
{
public delegate void MinimodeChangedEventHandler(object sender, bool isOn);
public enum PlayerDisplayState { Normal, Minimized, Compact }
public class QualityComparer : IComparer<string>
{
public int Compare(string x, string y)
{
string[] xArr = x.Split('p');
string[] yArr = y.Split('p');
int qualityA = int.Parse(xArr[0]);
int qualityB = int.Parse(yArr[0]);
int framerateA = 30;
int framerateB = 30;
if (!string.IsNullOrWhiteSpace(xArr[1]))
framerateA = int.Parse(xArr[1]);
if (!string.IsNullOrWhiteSpace(yArr[1]))
framerateB = int.Parse(yArr[1]);
if (qualityA > qualityB)
return 1;
else if (qualityA < qualityB)
return -1;
else
return framerateA - framerateB > 0 ? 1 : -1;
}
}
/// <summary>
/// Custom controls for media player. MARKUP IS IN **Themes/Generic.xaml**!!!
/// </summary>
public sealed class PlayerControls : MediaTransportControls
{
public event RoutedEventHandler CloseRequested;
public event MinimodeChangedEventHandler MiniModeChanged;
public event RoutedEventHandler NextRequested;
#region Controls variables
Button minimize;
Button close;
Button miniview;
Button play;
Button next;
Button volumeMenu;
Button mute;
Button live;
Button fwd;
Button bwd;
Button captionsMenu;
Button settingsMenu;
Button fullscreen;
Button drag;
TextBlock title;
TextBlock channel;
TextBlock elapsed;
TextBlock remain;
Slider volume;
Slider seek;
ProgressBar seekIndicator;
ComboBox captions;
ComboBox quality;
ToggleSwitch captionsSwitch;
StackPanel rightFooter;
StackPanel leftFooter;
StackPanel rightHeader;
StackPanel centerStack;
Grid header;
Grid footer;
Grid center;
#endregion
PlayerDisplayState State { get; set; } = PlayerDisplayState.Normal;
public MediaElement Player { get; set; }
public PlayerAdvert Advert;
public LiveCaptions Caption;
TimeSpan timecodeBackup;
bool needUpdateTimecode = false;
public Video Meta { get; set; }
public IReadOnlyList<ClosedCaptionTrackInfo> ClosedCaptions { get; set; }
public MediaStreamInfoSet MediaStreams { get; set; }
Queue<Action> queue = new Queue<Action>();
bool isReady = false;
public PlayerControls()
{
DefaultStyleKey = typeof(PlayerControls);
}
protected override void OnApplyTemplate()
{
AssignControls();
isReady = true;
minimize.Click += Minimize_Click;
close.Click += Close_Click;
miniview.Click += Miniview_Click;
next.Click += Next_Click;
volume.ValueChanged += Volume_ValueChanged;
live.Click += Live_Click;
captionsSwitch.Toggled += CaptionsSwitch_Toggled;
captions.SelectionChanged += Captions_SelectionChanged;
quality.SelectionChanged += Quality_SelectionChanged;
seek.ValueChanged += Seek_ValueChanged;
if (queue.Count > 0)
foreach (Action i in queue)
i();
base.OnApplyTemplate();
}
void AssignControls()
{
minimize = GetTemplateChild("MinimizeButton") as Button;
close = GetTemplateChild("CloseButton") as Button;
miniview = GetTemplateChild("CompactOverlayButton") as Button;
play = GetTemplateChild("PlayPauseButton") as Button;
next = GetTemplateChild("NextButton") as Button;
volumeMenu = GetTemplateChild("VolumeMenuButton") as Button;
mute = GetTemplateChild("AudioMuteButton") as Button;
live = GetTemplateChild("PlayLiveButton") as Button;
fwd = GetTemplateChild("SkipForwardButton") as Button;
bwd = GetTemplateChild("SkipBackwardButton") as Button;
captionsMenu = GetTemplateChild("CaptionsMenuButton") as Button;
settingsMenu = GetTemplateChild("QualityMenuButton") as Button;
fullscreen = GetTemplateChild("FullWindowButton") as Button;
drag = GetTemplateChild("drag") as Button;
Advert = GetTemplateChild("AdvertControl") as PlayerAdvert;
Caption = GetTemplateChild("CaptionControl") as LiveCaptions;
title = GetTemplateChild("title") as TextBlock;
channel = GetTemplateChild("channel") as TextBlock;
elapsed = GetTemplateChild("TimeElapsedElement") as TextBlock;
remain = GetTemplateChild("TimeRemainingElement") as TextBlock;
volume = GetTemplateChild("VolumeSlider") as Slider;
seek = GetTemplateChild("ProgressSlider") as Slider;
seekIndicator = GetTemplateChild("SeekIndicator") as ProgressBar;
captions = GetTemplateChild("CaptionsSelector") as ComboBox;
quality = GetTemplateChild("QualitySelector") as ComboBox;
captionsSwitch = GetTemplateChild("CaptionsToggleSwitch") as ToggleSwitch;
rightFooter = GetTemplateChild("RightFooterControls") as StackPanel;
leftFooter = GetTemplateChild("LeftFooterControls") as StackPanel;
rightHeader = GetTemplateChild("RightHeaderControls") as StackPanel;
centerStack = GetTemplateChild("centerControls") as StackPanel;
header = GetTemplateChild("header") as Grid;
footer = GetTemplateChild("footer") as Grid;
center = GetTemplateChild("center") as Grid;
}
private void Seek_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
{
seekIndicator.Value = seek.Value;
}
private async void Quality_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (Meta.Snippet.LiveBroadcastContent == "live")
goto SetQuality;
if(!needUpdateTimecode)
timecodeBackup = Player.Position;
needUpdateTimecode = true;
Player.Pause();
Player.Source = null;
SetQuality:
object info = (quality.SelectedItem as ComboBoxItem).Tag;
if (info is MuxedStreamInfo)
Player.SetPlaybackSource(MediaSource.CreateFromUri((info as MuxedStreamInfo).Url.ToUri()));
else if (info is VideoStreamInfo)
Player.SetPlaybackSource(MediaSource.CreateFromUri(await ManifestGenerator.GetManifest(Meta, info as VideoStreamInfo, MediaStreams)));
else if (info is StreamQuality)
Player.SetPlaybackSource(MediaSource.CreateFromUri((info as StreamQuality).Url));
}
private void Captions_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if(Caption.IsActive)
{
Caption.Close();
Caption.Initialize((captions.SelectedItem as ComboBoxItem).Tag as ClosedCaptionTrackInfo);
}
}
private void CaptionsSwitch_Toggled(object sender, RoutedEventArgs e)
{
if(captionsSwitch.IsOn)
Caption.Initialize((captions.SelectedItem as ComboBoxItem).Tag as ClosedCaptionTrackInfo);
else
Caption.Close();
}
private void Live_Click(object sender, RoutedEventArgs e)
{
Player.Position = Player.NaturalDuration.TimeSpan;
}
private void Next_Click(object sender, RoutedEventArgs e)
{
NextRequested.Invoke(sender, e);
}
private void Miniview_Click(object sender, RoutedEventArgs e)
{
if (State == PlayerDisplayState.Compact)
Maximize();
else
EnterMiniview();
}
public void UpdateVolumeIcon()
{
Volume_ValueChanged(this, null);
}
private void Volume_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
{
double v = volume.Value;
if (v == 0 || Player.IsMuted)
volumeMenu.Content = mute.Content = "\xE74F";
else if (v <= 25 && v > 0)
volumeMenu.Content = mute.Content = "\xE992";
else if (v <= 50 && v > 25)
volumeMenu.Content = mute.Content = "\xE993";
else if (v <= 75 && v > 50)
volumeMenu.Content = mute.Content = "\xE994";
else if (v > 75)
volumeMenu.Content = mute.Content = "\xE995";
}
private void Player_MediaOpened(object sender, RoutedEventArgs args)
{
if (!needUpdateTimecode)
return;
needUpdateTimecode = false;
Player.Position = timecodeBackup;
}
private void Close_Click(object sender, RoutedEventArgs e)
{
CloseRequested?.Invoke(sender, e);
}
private void Minimize_Click(object sender, RoutedEventArgs e)
{
if (State == PlayerDisplayState.Normal)
Minimize();
else
Maximize();
}
public void Minimize()
{
if (State == PlayerDisplayState.Minimized)
return;
header.Children.Remove(minimize);
center.Children.Add(minimize);
rightHeader.Children.Remove(close);
center.Children.Add(close);
leftFooter.Children.Remove(play);
centerStack.Children.Add(play);
rightFooter.Children.Remove(fwd);
centerStack.Children.Add(fwd);
rightFooter.Children.Remove(bwd);
centerStack.Children.Insert(0, bwd);
header.Visibility = Visibility.Collapsed;
center.Visibility = Visibility.Visible;
footer.Visibility = Visibility.Collapsed;
minimize.Content = "\xE010";
MiniModeChanged.Invoke(this, true);
Caption.Minimize();
State = PlayerDisplayState.Minimized;
}
public void Maximize()
{
if (State == PlayerDisplayState.Normal)
return;
if(State == PlayerDisplayState.Compact)
{
center.Children.Remove(miniview);
rightHeader.Children.Add(miniview);
centerStack.Children.Remove(play);
leftFooter.Children.Insert(0, play);
centerStack.Children.Remove(fwd);
rightFooter.Children.Insert(0, fwd);
centerStack.Children.Remove(bwd);
rightFooter.Children.Insert(0, bwd);
miniview.Margin = new Thickness();
miniview.Width = 50;
miniview.Height = 50;
}
else
{
center.Children.Remove(minimize);
header.Children.Insert(0, minimize);
center.Children.Remove(close);
rightHeader.Children.Insert(0, close);
centerStack.Children.Remove(play);
leftFooter.Children.Insert(0, play);
centerStack.Children.Remove(fwd);
rightFooter.Children.Insert(0, fwd);
centerStack.Children.Remove(bwd);
rightFooter.Children.Insert(0, bwd);
MiniModeChanged.Invoke(this, false);
}
drag.Visibility = Visibility.Collapsed;
header.Visibility = Visibility.Visible;
center.Visibility = Visibility.Collapsed;
footer.Visibility = Visibility.Visible;
miniview.Content = "\xE2B3";
minimize.Content = "\xE011";
Caption.Maximize();
State = PlayerDisplayState.Normal;
}
public void EnterMiniview()
{
if (State == PlayerDisplayState.Compact)
return;
rightHeader.Children.Remove(miniview);
center.Children.Add(miniview);
leftFooter.Children.Remove(play);
centerStack.Children.Add(play);
rightFooter.Children.Remove(fwd);
centerStack.Children.Add(fwd);
rightFooter.Children.Remove(bwd);
centerStack.Children.Insert(0, bwd);
drag.Visibility = Visibility.Visible;
header.Visibility = Visibility.Collapsed;
center.Visibility = Visibility.Visible;
footer.Visibility = Visibility.Collapsed;
miniview.Margin = new Thickness(0, 32, 0, 0);
miniview.Width = 47;
miniview.Height = 47;
miniview.Content = "\xE2B4";
Caption.Minimize();
State = PlayerDisplayState.Compact;
}
public async void Load(Video meta)
{
if(!isReady)
{
queue.Enqueue(() => Load(meta));
return;
}
Player.MediaOpened += Player_MediaOpened;
Meta = meta;
title.Text = meta.Snippet.Title;
channel.Text = meta.Snippet.ChannelTitle;
MediaStreams = await new YoutubeClient().GetVideoMediaStreamInfosAsync(meta.Id);
if (meta.Snippet.LiveBroadcastContent == "none")
{
ClosedCaptions = await new YoutubeClient().GetVideoClosedCaptionTrackInfosAsync(meta.Id);
/*foreach (MuxedStreamInfo i in MediaStreams.Muxed)
quality.Items.Add(new ComboBoxItem
{
Content = $"{i.VideoQualityLabel} (muxed)",
Tag = i
});
foreach (VideoStreamInfo i in MediaStreams.Video)
quality.Items.Add(new ComboBoxItem
{
Content = $"{i.VideoQualityLabel} (video-only)",
Tag = i
});
foreach (AudioStreamInfo i in MediaStreams.Audio)
quality.Items.Add(new ComboBoxItem
{
Content = $"{i.Bitrate} (audio-only)",
Tag = i
});*/
uint screenHeight = DisplayInformation.GetForCurrentView().ScreenHeightInRawPixels;
List<string> qualityList = MediaStreams.GetAllVideoQualityLabels().ToList();
qualityList.Sort(new QualityComparer());
qualityList.Reverse();
foreach (string i in qualityList)
{
object tag;
if (MediaStreams.Muxed.Any(m => m.VideoQualityLabel == i && m.Resolution.Height <= screenHeight))
tag = MediaStreams.Muxed.Find(m => m.VideoQualityLabel == i);
else if (MediaStreams.Video.Any(m => m.VideoQualityLabel == i && m.Resolution.Height <= screenHeight && m.Container.GetFileExtension() == "webm"))
tag = MediaStreams.Video.Find(m => m.VideoQualityLabel == i && m.Container.GetFileExtension() == "webm");
else
continue;
quality.Items.Add(new ComboBoxItem
{
Content = i,
Tag = tag
});
}
string s = SettingsStorage.VideoQuality == "remember" ? SettingsStorage.RememberedQuality : SettingsStorage.VideoQuality;
if (quality.Items.Any(i => (i as ComboBoxItem).Content as string == s))
quality.SelectedItem = quality.Items.Find(i => (i as ComboBoxItem).Content as string == s);
else
quality.SelectedIndex = 0;
if (ClosedCaptions.Count == 0)
{
captionsMenu.Visibility = Visibility.Collapsed;
return;
}
foreach (ClosedCaptionTrackInfo i in ClosedCaptions)
captions.Items.Add(new ComboBoxItem
{
Content = string.Format("{0} {1}", CultureInfo.GetCultureInfo(i.Language.Code).DisplayName, i.IsAutoGenerated ? ResourceLoader.GetForCurrentView("VideoPage").GetString("/VideoPage/generatedCaption") : ""),
Tag = i
});
ClosedCaptionTrackInfo item = ClosedCaptions.Find(i => SettingsStorage.RelevanceLanguage.Contains(i.Language.Code)) ?? ClosedCaptions.Find(i => "en-US".Contains(i.Language.Code));
if (item == null)
item = ClosedCaptions.First();
captions.SelectedItem = captions.Items.Find(i => (i as ComboBoxItem).Tag as ClosedCaptionTrackInfo == item);
Caption.Player = Player;
}
else
{
captionsMenu.Visibility = Visibility.Collapsed;
seek.Visibility = Visibility.Collapsed;
live.Visibility = Visibility.Visible;
remain.Visibility = Visibility.Collapsed;
elapsed.FontSize = 24;
Grid.SetRow(elapsed, 0);
Grid.SetRowSpan(elapsed, 2);
elapsed.HorizontalAlignment = HorizontalAlignment.Right;
fwd.Visibility = Visibility.Collapsed;
bwd.Visibility = Visibility.Collapsed;
List<StreamQuality> list = await ManifestGenerator.ResolveLiveSteream(MediaStreams.HlsLiveStreamUrl);
foreach (StreamQuality i in list)
quality.Items.Add(new ComboBoxItem
{
Content = i.Resolution,
Tag = i
});
string s = SettingsStorage.VideoQuality == "remember" ? SettingsStorage.RememberedQuality : SettingsStorage.VideoQuality;
if (quality.Items.Any(i => (i as ComboBoxItem).Content as string == s))
quality.SelectedItem = quality.Items.Find(i => (i as ComboBoxItem).Content as string == s);
else
quality.SelectedIndex = 0;
}
}
}
}