Archived
1
0

- 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
This commit is contained in:
Michael Gordeev
2019-05-14 19:34:04 +03:00
parent ade5c85eed
commit cebbf4fef5
29 changed files with 1244 additions and 798 deletions
+1
View File
@@ -267,6 +267,7 @@ namespace FoxTube
SettingsStorage.SaveData();
DownloadAgent.QuitPrompt();
Controls.Player.ManifestGenerator.ClearRoaming();
deferral.Complete();
Analytics.TrackEvent("Session terminated");
}
+4
View File
@@ -19,6 +19,8 @@
- FoxTube pro price is now displayed in menu
- Fixed crashes on opening links which don't contain http(s) prefix
- Fixed backward navigation with minimized video
- Player re-design
- Added quality selector to live streams playback
</en-US>
<ru-RU>### Что нового:
- Исправлена проблема получения истории, "Посмотреть позже" и рекомендаций
@@ -37,6 +39,8 @@
- Теперь на кнопке отключения рекламы отображается текущая цена
- Исправлены вылеты при попытке открыть ссылку не содержащую http(s) префикс
- Исправлена обратная навигация при уменьшенном видео
- Редизайн плеера
- Добавлено меню выбора качества для прямых эфиров
</ru-RU>
</content>
</item>
+200 -18
View File
@@ -1,26 +1,42 @@
using System;
using System.Threading.Tasks;
using YoutubeExplode.Models.MediaStreams;
using Windows.Storage;
using AngleSharp.Dom.Html;
using AngleSharp.Parser.Html;
using Google.Apis.YouTube.v3.Data;
using System.Xml;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web;
using System.Xml;
using Windows.Storage;
using YoutubeExplode.Models.MediaStreams;
namespace FoxTube.Controls.Player
{
public static class ManifestGenerator
{
static StorageFolder roaming = ApplicationData.Current.RoamingFolder;
static readonly StorageFolder roaming = ApplicationData.Current.RoamingFolder;
public static async Task<Uri> GetManifest(Video meta, VideoStreamInfo requestedQuality, MediaStreamInfoSet list)
{
StorageFile manifest = await roaming.CreateFileAsync("manifest.mpd", CreationCollisionOption.ReplaceExisting);
StorageFile manifest;
try
{
manifest = await roaming.CreateFileAsync("manifest.mpd", CreationCollisionOption.ReplaceExisting);
}
catch
{
manifest = await roaming.CreateFileAsync("manifest.mpd", CreationCollisionOption.GenerateUniqueName);
}
XmlDocument doc = new XmlDocument();
XmlElement mpd = doc.CreateElement("MPD");
mpd.SetAttribute("mediaPresentationDuration", meta.ContentDetails.Duration);
mpd.SetAttribute("minBufferTime", "PT2S");
XmlElement period = doc.CreateElement("Period");
period.SetAttribute("duration", meta.ContentDetails.Duration);
@@ -31,7 +47,29 @@ namespace FoxTube.Controls.Player
videoMeta.SetAttribute("contentType", "video");
videoSet.AppendChild(videoMeta);
AppendVideoSet(doc, videoSet, list.Video);
StreamInfo streamInfo = await GetInfoAsync(meta, requestedQuality, list);
XmlElement representation = doc.CreateElement("Representation");
representation.SetAttribute("bandwidth", GetBandwidth(requestedQuality.VideoQuality));
representation.SetAttribute("height", requestedQuality.Resolution.Height.ToString());
representation.SetAttribute("width", requestedQuality.Resolution.Width.ToString());
representation.SetAttribute("id", "1");
representation.SetAttribute("codecs", requestedQuality.VideoEncoding.ToString());
representation.SetAttribute("mimeType", $"video/{requestedQuality.Container.GetFileExtension()}");
XmlElement baseUrl = doc.CreateElement("BaseURL");
baseUrl.InnerText = requestedQuality.Url;
representation.AppendChild(baseUrl);
XmlElement segmentBase = doc.CreateElement("SegmentBase");
segmentBase.SetAttribute("indexRange", streamInfo.Video.IndexRange);
representation.AppendChild(segmentBase);
XmlElement initialization = doc.CreateElement("Initialization");
initialization.SetAttribute("range", streamInfo.Video.InitRange);
segmentBase.AppendChild(initialization);
videoSet.AppendChild(representation);
XmlElement audioSet = doc.CreateElement("AdaptationSet");
XmlElement audioMeta = doc.CreateElement("ContentComponent");
@@ -39,15 +77,26 @@ namespace FoxTube.Controls.Player
audioSet.AppendChild(audioMeta);
XmlElement audio = doc.CreateElement("Representation");
audio.SetAttribute("bandwidth", "100000");
audio.SetAttribute("id", (list.Video.Count + 1).ToString());
audio.SetAttribute("mimeType", $"audio/{list.Audio.First().Container.GetFileExtension()}");
audio.SetAttribute("bandwidth", "200000");
audio.SetAttribute("id", "2");
audio.SetAttribute("sampleRate", streamInfo.Audio.SampleRate);
audio.SetAttribute("numChannels", streamInfo.Audio.ChannelsCount);
audio.SetAttribute("codecs", list.Audio.First(i => i.Container.GetFileExtension() == "webm").AudioEncoding.ToString());
audio.SetAttribute("mimeType", $"audio/{list.Audio.First(i => i.Container.GetFileExtension() == "webm").Container.GetFileExtension()}");
audioSet.AppendChild(audio);
XmlElement audioUrl = doc.CreateElement("BaseURL");
audioUrl.InnerText = list.Audio.First().Url;
audioUrl.InnerText = list.Audio.First(i => i.Container.GetFileExtension() == "webm").Url;
audio.AppendChild(audioUrl);
audioSet.AppendChild(audio);
XmlElement audioSegmentBase = doc.CreateElement("SegmentBase");
audioSegmentBase.SetAttribute("indexRange", streamInfo.Audio.IndexRange);
audioSegmentBase.SetAttribute("indexRangeExact", "true");
audio.AppendChild(audioSegmentBase);
XmlElement audioInit = doc.CreateElement("Initialization");
audioInit.SetAttribute("range", streamInfo.Audio.InitRange);
audioSegmentBase.AppendChild(audioInit);
doc.AppendChild(mpd);
mpd.AppendChild(period);
@@ -56,10 +105,69 @@ namespace FoxTube.Controls.Player
doc.Save(await manifest.OpenStreamForWriteAsync());
return "ms-appdata:///roaming/manifest.mpd".ToUri();
//TODO: Fix this shit. It doesn't work
return $"ms-appdata:///roaming/{manifest.Name}".ToUri();
}
private static void AppendVideoSet(XmlDocument doc, XmlElement root, IReadOnlyList<VideoStreamInfo> list)
private static async Task<StreamInfo> GetInfoAsync(Video info, VideoStreamInfo requestedQuality, MediaStreamInfoSet list)
{
HttpClient http = new HttpClient();
string response = HttpUtility.HtmlDecode(await http.GetStringAsync($"https://youtube.com/embed/{info.Id}?disable_polymer=true&hl=en"));
IHtmlDocument videoEmbedPageHtml = new HtmlParser().Parse(response);
string playerConfigRaw = Regex.Match(videoEmbedPageHtml.Source.Text,
@"yt\.setConfig\({'PLAYER_CONFIG': (?<Json>\{[^\{\}]*(((?<Open>\{)[^\{\}]*)+((?<Close-Open>\})[^\{\}]*)+)*(?(Open)(?!))\})")
.Groups["Json"].Value;
JToken playerConfigJson = JToken.Parse(playerConfigRaw);
string sts = playerConfigJson.SelectToken("sts").Value<string>();
string eurl = WebUtility.UrlEncode($"https://youtube.googleapis.com/v/{info.Id}");
string url = $"https://youtube.com/get_video_info?video_id={info.Id}&el=embedded&sts={sts}&eurl={eurl}&hl=en";
string raw = await http.GetStringAsync(url);
Dictionary<string, string> videoInfoDic = SplitQuery(raw);
StreamInfo si = new StreamInfo();
List<Dictionary<string, string>> adaptiveStreamInfosUrl = videoInfoDic.GetValueOrDefault("adaptive_fmts").Split(',').Select(SplitQuery).ToList();
Dictionary<string, string> video = adaptiveStreamInfosUrl.Find(i => i["quality_label"] == requestedQuality.VideoQualityLabel && i["type"].Contains(requestedQuality.Container.GetFileExtension()));
Dictionary<string, string> audio = adaptiveStreamInfosUrl.Find(i => i.ContainsKey("audio_sample_rate") && i["type"].Contains("webm"));
si.Video.IndexRange = video["index"];
si.Audio.ChannelsCount = audio["audio_channels"];
si.Audio.IndexRange = audio["index"];
si.Audio.SampleRate = audio["audio_sample_rate"];
return si;
}
public static Dictionary<string, string> SplitQuery(string query)
{
Dictionary<string, string> dic = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
string[] paramsEncoded = query.TrimStart('?').Split("&");
foreach (string paramEncoded in paramsEncoded)
{
string param = WebUtility.UrlDecode(paramEncoded);
// Look for the equals sign
int equalsPos = param.IndexOf('=');
if (equalsPos <= 0)
continue;
// Get the key and value
string key = param.Substring(0, equalsPos);
string value = equalsPos < param.Length
? param.Substring(equalsPos + 1)
: string.Empty;
// Add to dictionary
dic[key] = value;
}
return dic;
}
private static void AppendVideoSet(XmlDocument doc, XmlElement root, List<VideoStreamInfo> list)
{
for (int k = 0; k < list.Count; k++)
{
@@ -87,7 +195,7 @@ namespace FoxTube.Controls.Player
private static string GetBandwidth(VideoQuality quality)
{
switch(quality)
switch (quality)
{
case VideoQuality.High4320:
return $"16763040";
@@ -114,5 +222,79 @@ namespace FoxTube.Controls.Player
return $"100000";
}
}
public static async Task<List<StreamQuality>> ResolveLiveSteream(string url)
{
List<StreamQuality> list = new List<StreamQuality>();
string playlistRaw = await new HttpClient().GetStringAsync(url);
List<string> streamsRaw = playlistRaw.Split("#EXT-X-STREAM-INF:").ToList();
streamsRaw.RemoveAt(0);
List<Dictionary<string, string>> streams = new List<Dictionary<string, string>>();
foreach (string i in streamsRaw)
{
Dictionary<string, string> item = new Dictionary<string, string>();
string[] par = i.Split('\n');
item.Add("URL", par[1]);
par = par[0].Split(',');
foreach (string k in par)
{
string[] pair = k.Split('=');
if (pair.Length < 2)
continue;
item[pair[0]] = pair[1];
}
streams.Add(item);
}
foreach (var i in streams)
{
StreamQuality item = new StreamQuality();
item.Resolution = $"{i["RESOLUTION"].Split('x')[1]}p";
item.Url = i["URL"].ToUri();
list.Add(item);
}
list.Add(new StreamQuality
{
Resolution = "Auto",
Url = url.ToUri()
});
list.Reverse();
return list;
}
public static async void ClearRoaming()
{
IReadOnlyList<StorageFile> items = await roaming.GetFilesAsync();
foreach (StorageFile f in items)
await f.DeleteAsync(StorageDeleteOption.PermanentDelete);
}
}
public class StreamInfo
{
public class VideoInfo
{
public string IndexRange { get; set; }
public string InitRange => $"0-{int.Parse(IndexRange.Split('-')[0]) - 1}";
}
public class AudioInfo
{
public string IndexRange { get; set; }
public string InitRange => $"0-{int.Parse(IndexRange.Split('-')[0]) - 1}";
public string SampleRate { get; set; }
public string ChannelsCount { get; set; }
}
public VideoInfo Video { get; } = new VideoInfo();
public AudioInfo Audio { get; } = new AudioInfo();
}
public class StreamQuality
{
public Uri Url { get; set; }
public string Resolution { get; set; }
}
}
+2 -1
View File
@@ -59,7 +59,8 @@ namespace FoxTube
private static void Save()
{
ApplicationData.Current.RoamingSettings.Values[$"history-{SecretsVault.AccountId}"] = JsonConvert.SerializeObject(Items);
try { ApplicationData.Current.RoamingSettings.Values[$"history-{SecretsVault.AccountId}"] = JsonConvert.SerializeObject(Items); }
catch { }
}
public static void Load()
+1 -1
View File
@@ -180,7 +180,7 @@ namespace FoxTube
{
if (storage.Values["mature"] == null)
{
storage.Values["mature"] = MatureState.Blocked;
storage.Values["mature"] = (int)MatureState.Blocked;
return MatureState.Blocked;
}
else return (MatureState)storage.Values["mature"];
+15 -13
View File
@@ -52,21 +52,23 @@ namespace FoxTube.Controls
else
rating.Text = comment.Snippet.TopLevelComment.Snippet.LikeCount.HasValue ? comment.Snippet.TopLevelComment.Snippet.LikeCount.ToString() : "";
if (item.Snippet.AuthorChannelId.ToString().Split('"')[3] == SecretsVault.AccountId)
if(item.Snippet.AuthorChannelId != null)
{
(specialAuthor.Child as TextBlock).Text = comment.Snippet.TopLevelComment.Snippet.AuthorDisplayName;
specialAuthor.Visibility = Visibility.Visible;
author.Visibility = Visibility.Collapsed;
editBtn.Visibility = Visibility.Visible;
if (item.Snippet.AuthorChannelId.ToString().Split('"')[3] == SecretsVault.AccountId)
{
(specialAuthor.Child as TextBlock).Text = comment.Snippet.TopLevelComment.Snippet.AuthorDisplayName;
specialAuthor.Visibility = Visibility.Visible;
author.Visibility = Visibility.Collapsed;
editBtn.Visibility = Visibility.Visible;
}
else if (item.Snippet.AuthorChannelId.ToString().Split('"')[3] == item.Snippet.ChannelId)
{
(specialAuthor.Child as TextBlock).Text = comment.Snippet.TopLevelComment.Snippet.AuthorDisplayName;
specialAuthor.Visibility = Visibility.Visible;
author.Visibility = Visibility.Collapsed;
}
}
else if (item.Snippet.AuthorChannelId.ToString().Split('"')[3] == item.Snippet.ChannelId)
{
(specialAuthor.Child as TextBlock).Text = comment.Snippet.TopLevelComment.Snippet.AuthorDisplayName;
specialAuthor.Visibility = Visibility.Visible;
author.Visibility = Visibility.Collapsed;
}
else
author.Text = comment.Snippet.TopLevelComment.Snippet.AuthorDisplayName;
author.Text = comment.Snippet.TopLevelComment.Snippet.AuthorDisplayName;
meta.Text = string.Format("{0} {1}", Methods.GetAgo(comment.Snippet.TopLevelComment.Snippet.PublishedAt.Value), comment.Snippet.TopLevelComment.Snippet.UpdatedAt != comment.Snippet.TopLevelComment.Snippet.PublishedAt ? resources.GetString("/CommentsPage/edited") : "");
Methods.FormatText(ref text, comment.Snippet.TopLevelComment.Snippet.TextDisplay);
+17 -3
View File
@@ -17,7 +17,9 @@ namespace FoxTube.Controls
set => text.FontSize = value;
}
public MediaPlayer Player { get; set; }
public bool IsActive => track != null;
public MediaElement Player { get; set; }
DispatcherTimer timer = new DispatcherTimer() { Interval = TimeSpan.FromMilliseconds(10) };
ClosedCaption currentCaption = null;
@@ -32,7 +34,7 @@ namespace FoxTube.Controls
private void UpdateCaption(object sender, object e)
{
currentCaption = track.Captions.Find(i => i.Offset <= Player.PlaybackSession.Position && i.Offset + i.Duration > Player.PlaybackSession.Position);
currentCaption = track.Captions.Find(i => i.Offset <= Player.Position && i.Offset + i.Duration > Player.Position);
if (currentCaption != null)
text.Text = currentCaption.Text;
@@ -51,10 +53,22 @@ namespace FoxTube.Controls
public void Close()
{
timer.Stop();
track = null;
currentCaption = null;
Visibility = Visibility.Collapsed;
timer.Stop();
}
public void Minimize()
{
Margin = new Thickness(0, 0, 0, 20);
text.FontSize = 15;
}
public void Maximize()
{
Margin = new Thickness(0, 0, 0, 55);
text.FontSize = 24;
}
}
}
+407 -245
View File
@@ -1,38 +1,111 @@
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.Media.Playback;
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 QualityChangedEventHandler(object sender, MediaStreamInfo requestedQuality, MediaStreamInfoSet list);
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 RoutedEventHandler LiveRequested;
public event MinimodeChangedEventHandler MiniModeChanged;
public event RoutedEventHandler NextRequested;
public event Event MuteClicked;
public event QualityChangedEventHandler QualityChanged;
#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;
public MediaPlayer Player;
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; }
@@ -47,31 +120,22 @@ namespace FoxTube
protected override void OnApplyTemplate()
{
AssignControls();
isReady = true;
Advert = GetTemplateChild("ad") as PlayerAdvert;
minimize.Click += Minimize_Click;
close.Click += Close_Click;
miniview.Click += Miniview_Click;
(GetTemplateChild("close") as Button).Click += Close_Click;
(GetTemplateChild("compactClose") as Button).Click += Close_Click;
next.Click += Next_Click;
volume.ValueChanged += Volume_ValueChanged;
live.Click += Live_Click;
(GetTemplateChild("minimize") as Button).Click += Minimize_Click;
(GetTemplateChild("maximize") as Button).Click += Minimize_Click;
(GetTemplateChild("CompactOverlayButton") as Button).Click += CompactOverlay_Click;
(GetTemplateChild("qualitySelector") as ComboBox).SelectionChanged += QualitySelector_SelectionChanged;
(GetTemplateChild("ccSwitch") as ToggleSwitch).Toggled += CcSwitch_Toggled;
(GetTemplateChild("ccSelector") as ComboBox).SelectionChanged += CcSelector_SelectionChanged;
(GetTemplateChild("next") as Button).Click += (s, e) => NextRequested.Invoke(s, e);
(GetTemplateChild("AudioMuteButton") as Button).Click += Mute_Click;
(GetTemplateChild("VolumeSlider") as Slider).ValueChanged += Volume_ValueChanged;
(GetTemplateChild("ProgressSlider") as Slider).ValueChanged += ProgressSlider_ValueChanged;
(GetTemplateChild("goLive") as Button).Click += (s, e) => LiveRequested.Invoke(s, e);
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)
@@ -80,96 +144,135 @@ namespace FoxTube
base.OnApplyTemplate();
}
private void Minimize_Click(object sender, RoutedEventArgs e)
void AssignControls()
{
if (sender == (GetTemplateChild("minimize") as Button))
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)
{
MiniModeChanged.Invoke(this, true);
SetMinimized();
}
else
{
MiniModeChanged.Invoke(this, false);
SetNormal();
Caption.Close();
Caption.Initialize((captions.SelectedItem as ComboBoxItem).Tag as ClosedCaptionTrackInfo);
}
}
public void Minimize()
private void CaptionsSwitch_Toggled(object sender, RoutedEventArgs e)
{
Minimize_Click(GetTemplateChild("minimize"), null);
}
private void ProgressSlider_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
{
(GetTemplateChild("compactSeek") as ProgressBar).Value = e.NewValue;
}
private void Mute_Click(object sender, RoutedEventArgs e)
{
if (((GetTemplateChild("AudioMuteButton") as Button).Content as FontIcon).Glyph == "\xE74F")
Volume_ValueChanged(this, null);
if(captionsSwitch.IsOn)
Caption.Initialize((captions.SelectedItem as ComboBoxItem).Tag as ClosedCaptionTrackInfo);
else
((GetTemplateChild("AudioMuteButton") as Button).Content as FontIcon).Glyph = ((GetTemplateChild("volume") as Button).Content as FontIcon).Glyph = "\xE74F";
Caption.Close();
}
MuteClicked?.Invoke();
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 = (GetTemplateChild("VolumeSlider") as Slider).Value;
if (v == 0)
((GetTemplateChild("AudioMuteButton") as Button).Content as FontIcon).Glyph = ((GetTemplateChild("volume") as Button).Content as FontIcon).Glyph = "\xE74F";
double v = volume.Value;
if (v == 0 || Player.IsMuted)
volumeMenu.Content = mute.Content = "\xE74F";
else if (v <= 25 && v > 0)
((GetTemplateChild("AudioMuteButton") as Button).Content as FontIcon).Glyph = ((GetTemplateChild("volume") as Button).Content as FontIcon).Glyph = "\xE992";
volumeMenu.Content = mute.Content = "\xE992";
else if (v <= 50 && v > 25)
((GetTemplateChild("AudioMuteButton") as Button).Content as FontIcon).Glyph = ((GetTemplateChild("volume") as Button).Content as FontIcon).Glyph = "\xE993";
volumeMenu.Content = mute.Content = "\xE993";
else if (v <= 75 && v > 50)
((GetTemplateChild("AudioMuteButton") as Button).Content as FontIcon).Glyph = ((GetTemplateChild("volume") as Button).Content as FontIcon).Glyph = "\xE994";
volumeMenu.Content = mute.Content = "\xE994";
else if (v > 75)
((GetTemplateChild("AudioMuteButton") as Button).Content as FontIcon).Glyph = ((GetTemplateChild("volume") as Button).Content as FontIcon).Glyph = "\xE995";
volumeMenu.Content = mute.Content = "\xE995";
}
private void CcSelector_SelectionChanged(object sender, SelectionChangedEventArgs e)
private void Player_MediaOpened(object sender, RoutedEventArgs args)
{
CcSwitch_Toggled((GetTemplateChild("ccSwitch") as ToggleSwitch), null);
}
if (!needUpdateTimecode)
return;
private void CcSwitch_Toggled(object sender, RoutedEventArgs e)
{
if((GetTemplateChild("captions") as LiveCaptions).Player == null)
(GetTemplateChild("captions") as LiveCaptions).Player = Player;
if ((sender as ToggleSwitch).IsOn)
(GetTemplateChild("captions") as LiveCaptions).Initialize(((GetTemplateChild("ccSelector") as ComboBox).SelectedItem as ComboBoxItem).Tag as ClosedCaptionTrackInfo);
else
(GetTemplateChild("captions") as LiveCaptions).Close();
}
private void QualitySelector_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
SettingsStorage.RememberedQuality = e.AddedItems[0] as string;
MediaStreamInfo item = MediaStreams.Muxed.Find(i => i.VideoQualityLabel.Contains(e.AddedItems[0] as string));
if (item == null)
item = MediaStreams.Video.Find(i => i.VideoQualityLabel == e.AddedItems[0] as string);
QualityChanged?.Invoke(sender, item, MediaStreams);
}
private void CompactOverlay_Click(object sender, RoutedEventArgs e)
{
if((sender as Button).Margin.Top > 0)
{
Button btnCompact = sender as Button;
(GetTemplateChild("center") as Grid).Children.Remove(btnCompact);
(GetTemplateChild("headerToolbar") as StackPanel).Children.Add(btnCompact);
btnCompact.Margin = new Thickness(0);
(btnCompact.Content as FontIcon).Glyph = "\xE2B3";
SetNormal();
}
else
SetCompactView();
needUpdateTimecode = false;
Player.Position = timecodeBackup;
}
private void Close_Click(object sender, RoutedEventArgs e)
@@ -177,179 +280,238 @@ namespace FoxTube
CloseRequested?.Invoke(sender, e);
}
public void SetCompactView()
private void Minimize_Click(object sender, RoutedEventArgs e)
{
Button btn = GetTemplateChild("CompactOverlayButton") as Button;
(GetTemplateChild("headerToolbar") as StackPanel).Children.Remove(btn);
(GetTemplateChild("center") as Grid).Children.Add(btn);
btn.Margin = new Thickness(0, 32, 0, 0);
(btn.Content as FontIcon).Glyph = "\xE2B4";
Button btnPlay = GetTemplateChild("PlayPauseButton") as Button;
(GetTemplateChild("leftStack") as StackPanel).Children.Remove(btnPlay);
(GetTemplateChild("centralStack") as StackPanel).Children.Add(btnPlay);
Button btnFwd = GetTemplateChild("SkipForwardButton") as Button;
(GetTemplateChild("rightStack") as StackPanel).Children.Remove(btnFwd);
(GetTemplateChild("centralStack") as StackPanel).Children.Insert(1, btnFwd);
Button btnBwd = GetTemplateChild("SkipBackwardButton") as Button;
(GetTemplateChild("rightStack") as StackPanel).Children.Remove(btnBwd);
(GetTemplateChild("centralStack") as StackPanel).Children.Insert(0, btnBwd);
(GetTemplateChild("header") as Grid).Visibility = Visibility.Collapsed;
(GetTemplateChild("centerBgr") as Grid).Visibility = Visibility.Visible;
(GetTemplateChild("center") as Grid).Visibility = Visibility.Visible;
(GetTemplateChild("footer") as Grid).Visibility = Visibility.Collapsed;
(GetTemplateChild("maximize") as Button).Visibility = Visibility.Collapsed;
(GetTemplateChild("compactClose") as Button).Visibility = Visibility.Collapsed;
(GetTemplateChild("dragholder") as Button).Visibility = Visibility.Visible;
(GetTemplateChild("captions") as LiveCaptions).Size = 15;
(GetTemplateChild("root") as Grid).RowDefinitions[1].Height = new GridLength(0);
if (State == PlayerDisplayState.Normal)
Minimize();
else
Maximize();
}
public void SetMinimized()
public void Minimize()
{
Button btnPlay = GetTemplateChild("PlayPauseButton") as Button;
(GetTemplateChild("leftStack") as StackPanel).Children.Remove(btnPlay);
(GetTemplateChild("centralStack") as StackPanel).Children.Add(btnPlay);
Button btnFwd = GetTemplateChild("SkipForwardButton") as Button;
(GetTemplateChild("rightStack") as StackPanel).Children.Remove(btnFwd);
(GetTemplateChild("centralStack") as StackPanel).Children.Insert(1, btnFwd);
Button btnBwd = GetTemplateChild("SkipBackwardButton") as Button;
(GetTemplateChild("rightStack") as StackPanel).Children.Remove(btnBwd);
(GetTemplateChild("centralStack") as StackPanel).Children.Insert(0, btnBwd);
(GetTemplateChild("header") as Grid).Visibility = Visibility.Collapsed;
(GetTemplateChild("centerBgr") as Grid).Visibility = Visibility.Visible;
(GetTemplateChild("center") as Grid).Visibility = Visibility.Visible;
(GetTemplateChild("footer") as Grid).Visibility = Visibility.Collapsed;
(GetTemplateChild("compactClose") as Button).Visibility = Visibility.Visible;
(GetTemplateChild("maximize") as Button).Visibility = Visibility.Visible;
(GetTemplateChild("captions") as LiveCaptions).Size = 15;
(GetTemplateChild("root") as Grid).RowDefinitions[1].Height = new GridLength(0);
}
public void SetNormal()
{
Button btnPlay = GetTemplateChild("PlayPauseButton") as Button;
(GetTemplateChild("centralStack") as StackPanel).Children.Remove(btnPlay);
(GetTemplateChild("leftStack") as StackPanel).Children.Insert(0, btnPlay);
Button btnFwd = GetTemplateChild("SkipForwardButton") as Button;
(GetTemplateChild("centralStack") as StackPanel).Children.Remove(btnFwd);
(GetTemplateChild("rightStack") as StackPanel).Children.Insert(0, btnFwd);
Button btnBwd = GetTemplateChild("SkipBackwardButton") as Button;
(GetTemplateChild("centralStack") as StackPanel).Children.Remove(btnBwd);
(GetTemplateChild("rightStack") as StackPanel).Children.Insert(0, btnBwd);
(GetTemplateChild("header") as Grid).Visibility = Visibility.Visible;
(GetTemplateChild("centerBgr") as Grid).Visibility = Visibility.Collapsed;
(GetTemplateChild("center") as Grid).Visibility = Visibility.Collapsed;
(GetTemplateChild("footer") as Grid).Visibility = Visibility.Visible;
(GetTemplateChild("captions") as LiveCaptions).Size = 24;
(GetTemplateChild("dragholder") as Button).Visibility = Visibility.Collapsed;
(GetTemplateChild("root") as Grid).RowDefinitions[1].Height = new GridLength(1, GridUnitType.Auto);
}
public void SetMeta(string title, string channel)
{
if (!isReady)
{
queue.Enqueue(() => SetMeta(title, channel));
if (State == PlayerDisplayState.Minimized)
return;
}
(GetTemplateChild("title") as TextBlock).Text = title;
(GetTemplateChild("author") as TextBlock).Text = channel;
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 SetCaptions(IReadOnlyList<ClosedCaptionTrackInfo> list)
public void Maximize()
{
if (!isReady)
if (State == PlayerDisplayState.Normal)
return;
if(State == PlayerDisplayState.Compact)
{
queue.Enqueue(() => SetCaptions(list));
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;
}
ClosedCaptions = list;
Player.MediaOpened += Player_MediaOpened;
if (list.Count > 0)
Meta = meta;
title.Text = meta.Snippet.Title;
channel.Text = meta.Snippet.ChannelTitle;
MediaStreams = await new YoutubeClient().GetVideoMediaStreamInfosAsync(meta.Id);
if (meta.Snippet.LiveBroadcastContent == "none")
{
foreach(ClosedCaptionTrackInfo i in list)
(GetTemplateChild("ccSelector") as ComboBox).Items.Add(new ComboBoxItem()
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 = list.Find(i => SettingsStorage.RelevanceLanguage.Contains(i.Language.Code)) ?? list.Find(i => "en-US".Contains(i.Language.Code));
ClosedCaptionTrackInfo item = ClosedCaptions.Find(i => SettingsStorage.RelevanceLanguage.Contains(i.Language.Code)) ?? ClosedCaptions.Find(i => "en-US".Contains(i.Language.Code));
if (item == null)
item = list.First();
item = ClosedCaptions.First();
(GetTemplateChild("ccSelector") as ComboBox).SelectedItem = (GetTemplateChild("ccSelector") as ComboBox).Items.Find(i => (i as ComboBoxItem).Tag == item);
captions.SelectedItem = captions.Items.Find(i => (i as ComboBoxItem).Tag as ClosedCaptionTrackInfo == item);
Caption.Player = Player;
}
else
(GetTemplateChild("cc") as Button).Visibility = Visibility.Collapsed;
}
public void SetQualities(MediaStreamInfoSet list)
{
if (!isReady)
{
queue.Enqueue(() => SetQualities(list));
return;
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;
}
MediaStreams = list;
List<VideoQuality> q = list.GetAllVideoQualities().ToList();
q.Sort();
q.Reverse();
List<string> labels = new List<string>();
q.ForEach(i => labels.Add(i.GetVideoQualityLabel()));
(GetTemplateChild("qualitySelector") as ComboBox).ItemsSource = labels;
string s = SettingsStorage.VideoQuality == "remember" ? SettingsStorage.RememberedQuality : SettingsStorage.VideoQuality;
if (labels.Contains(s))
(GetTemplateChild("qualitySelector") as ComboBox).SelectedItem = s;
else
(GetTemplateChild("qualitySelector") as ComboBox).SelectedIndex = 0;
}
public void SetStream(string url)
{
if (!isReady)
{
queue.Enqueue(() => SetStream(url));
return;
}
(GetTemplateChild("goLive") as Button).Visibility = Visibility.Visible;
(GetTemplateChild("sliderPan") as Grid).Children.Remove(GetTemplateChild("TimeElapsedElement") as TextBlock);
(GetTemplateChild("rightStack") as StackPanel).Children.Insert(0, GetTemplateChild("TimeElapsedElement") as TextBlock);
(GetTemplateChild("TimeElapsedElement") as TextBlock).VerticalAlignment = VerticalAlignment.Center;
(GetTemplateChild("TimeElapsedElement") as TextBlock).FontSize = 18;
(GetTemplateChild("TimeElapsedElement") as TextBlock).Margin = new Thickness(10, 0, 10, 0);
(GetTemplateChild("sliderPan") as Grid).Visibility = Visibility.Collapsed;
(GetTemplateChild("cc") as Button).Visibility = Visibility.Collapsed;
(GetTemplateChild("quality") as Button).Visibility = Visibility.Collapsed;
Player.Source = MediaSource.CreateFromUri(url.ToUri());
}
}
}
+6 -5
View File
@@ -8,16 +8,17 @@
mc:Ignorable="d"
d:DesignHeight="1080"
d:DesignWidth="1920"
RequestedTheme="Dark">
RequestedTheme="Dark"
Visibility="Collapsed">
<Grid Background="{StaticResource SystemChromeMediumColor}">
<MediaPlayerElement Name="videoSource" AreTransportControlsEnabled="True" PosterSource="ms-appx:///Assets/videoThumbSample.png">
<MediaPlayerElement.TransportControls>
<MediaElement Name="videoSource" AreTransportControlsEnabled="True" VolumeChanged="VideoSource_VolumeChanged" CurrentStateChanged="VideoSource_CurrentStateChanged" MarkerReached="VideoSource_MarkerReached" PosterSource="ms-appx:///Assets/videoThumbSample.png">
<MediaElement.TransportControls>
<foxtube:PlayerControls IsCompactOverlayButtonVisible="True" IsCompactOverlayEnabled="True"
IsFullWindowButtonVisible="True" IsFullWindowEnabled="True"
IsSkipBackwardButtonVisible="True" IsSkipBackwardEnabled="True"
IsSkipForwardButtonVisible="True" IsSkipForwardEnabled="True"/>
</MediaPlayerElement.TransportControls>
</MediaPlayerElement>
</MediaElement.TransportControls>
</MediaElement>
</Grid>
</UserControl>
+55 -114
View File
@@ -19,6 +19,8 @@ using Windows.Storage.Pickers;
using Windows.Storage;
using Windows.Media.MediaProperties;
using FoxTube.Controls.Player;
using System.Diagnostics;
using Windows.UI.Xaml.Media;
namespace FoxTube
{
@@ -33,15 +35,7 @@ namespace FoxTube
public event ObjectEventHandler MiniMode;
public PlayerControls Controls => videoSource.TransportControls as PlayerControls;
public MediaPlayer Player { get; } = new MediaPlayer();
public TimeSpan Position
{
get { return videoSource.MediaPlayer.PlaybackSession.Position; }
set { videoSource.MediaPlayer.PlaybackSession.Position = value; }
}
//TimeSpan timecodeBackup;
//bool needUpdateTimecode = false;
public MediaElement Player => videoSource;
SystemMediaTransportControls systemControls;
@@ -50,7 +44,7 @@ namespace FoxTube
InitializeComponent();
}
public async void Initialize(Video meta, string channelAvatar, bool privateMode = false)
public void Initialize(Video meta, string channelAvatar, bool privateMode = false)
{
incognito = privateMode;
item = meta;
@@ -61,58 +55,41 @@ namespace FoxTube
};
videoSource.PosterSource = new BitmapImage((meta.Snippet.Thumbnails.Maxres ?? meta.Snippet.Thumbnails.Medium).Url.ToUri());
Controls.SetMeta(meta.Snippet.Localized.Title, meta.Snippet.ChannelTitle);
if (item.Snippet.LiveBroadcastContent == "none")
{
InitializeContols();
// TODO: make ads live again
/*if (Methods.GetDuration(item.ContentDetails.Duration).TotalMinutes > 5)
Player.PlaybackSession.Add(new Windows.UI.Xaml.Media.TimelineMarker { Time = Methods.GetDuration(item.ContentDetails.Duration) - TimeSpan.FromMinutes(1) });
Controls.Load(item);
if (Methods.GetDuration(item.ContentDetails.Duration).TotalMinutes > 5)
videoSource.Markers.Add(new TimelineMarker { Time = Methods.GetDuration(item.ContentDetails.Duration) - TimeSpan.FromMinutes(1) });
if(Methods.GetDuration(item.ContentDetails.Duration).TotalMinutes >= 60)
for (int k = 1; k < Methods.GetDuration(item.ContentDetails.Duration).TotalMinutes / 30; k++)
videoSource.Markers.Add(new Windows.UI.Xaml.Media.TimelineMarker { Time = TimeSpan.FromMinutes(k * 30) });*/
videoSource.Markers.Add(new TimelineMarker { Time = TimeSpan.FromMinutes(k * 30) });
Controls.SetQualities(await new YoutubeClient().GetVideoMediaStreamInfosAsync(item.Id));
Controls.SetCaptions(await new YoutubeClient().GetVideoClosedCaptionTrackInfosAsync(item.Id));
if (!privateMode)
HistorySet.Update(history);
}
else if (item.Snippet.LiveBroadcastContent == "live")
{
InitializeContols();
Controls.IsSkipBackwardButtonVisible = false;
Controls.IsSkipForwardButtonVisible = false;
Controls.LiveRequested += Controls_LiveRequested;
object i = await new YoutubeClient().GetVideoMediaStreamInfosAsync(item.Id);
Controls.SetStream((await new YoutubeClient().GetVideoMediaStreamInfosAsync(item.Id)).HlsLiveStreamUrl);
Controls.Load(item);
}
else
videoSource.AreTransportControlsEnabled = false;
if (!privateMode)
HistorySet.Update(history);
Visibility = Visibility.Visible;
}
private void Controls_LiveRequested(object sender, RoutedEventArgs e)
{
Position = Player.PlaybackSession.NaturalDuration;
}
public void InitializeContols()
{
videoSource.SetMediaPlayer(Player);
Player.Volume = SettingsStorage.Volume;
Player.CurrentStateChanged += VideoSource_CurrentStateChanged;
Player.MediaOpened += VideoSource_MediaOpened;
Player.VolumeChanged += VideoSource_VolumeChanged;
videoSource.Volume = SettingsStorage.Volume;
videoSource.AutoPlay = SettingsStorage.Autoplay;
Controls.CloseRequested += Controls_CloseRequested;
Controls.NextRequested += (s, e) => NextClicked?.Invoke();
Controls.QualityChanged += Controls_QualityChanged;
Controls.MiniModeChanged += Controls_MiniModeChanged;
Controls.Player = Player;
Controls.Player = videoSource;
#region System Media Transport Controls
systemControls = SystemMediaTransportControls.GetForCurrentView();
@@ -131,6 +108,25 @@ namespace FoxTube
#endregion
}
private async void SystemControls_Engaged(SystemMediaTransportControls sender, SystemMediaTransportControlsButtonPressedEventArgs args)
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
switch (args.Button)
{
case SystemMediaTransportControlsButton.Play:
videoSource.Play();
break;
case SystemMediaTransportControlsButton.Pause:
videoSource.Pause();
break;
case SystemMediaTransportControlsButton.Next:
NextClicked?.Invoke();
break;
}
});
}
public void Controls_MiniModeChanged(object sender, bool e)
{
videoSource.IsFullWindow = false;
@@ -143,101 +139,46 @@ namespace FoxTube
Controls.Minimize();
}
private async void Controls_QualityChanged(object sender, MediaStreamInfo requestedQuality, MediaStreamInfoSet list)
{
Player.Pause();
Player.Source = MediaSource.CreateFromUri(await ManifestGenerator.GetManifest(item, requestedQuality as VideoStreamInfo, list));
//await ManifestGenerator.GetManifest(item, requestedQuality as VideoStreamInfo, list);
/*FileOpenPicker picker = new FileOpenPicker();
picker.FileTypeFilter.Add(".mpd");*/
//Player.Source = MediaSource.CreateFromUri("https://foxsharp.000webhostapp.com/dash_sample.mpd".ToUri());
/*timecodeBackup = videoSource.Position;
needUpdateTimecode = true;
if (requestedQuality is MuxedStreamInfo)
videoSource.Source = requestedQuality.Url.ToUri();
else
processor.Start(requestedQuality as VideoStreamInfo, list);*/
}
public void Controls_CloseRequested(object sender, RoutedEventArgs e)
{
if(systemControls != null)
systemControls.IsEnabled = false;
if (!incognito)
videoSource.Pause();
systemControls.IsEnabled = false;
videoSource.Source = null;
if (!incognito && item.Snippet.LiveBroadcastContent == "none")
{
history.LeftOn = Player.PlaybackSession.Position;
history.LeftOn = videoSource.Position;
HistorySet.Update(history);
}
Methods.MainPage.CloseVideo();
}
private async void SystemControls_Engaged(SystemMediaTransportControls sender, SystemMediaTransportControlsButtonPressedEventArgs args)
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
switch (args.Button)
{
case SystemMediaTransportControlsButton.Play:
Player.Play();
break;
case SystemMediaTransportControlsButton.Pause:
Player.Pause();
break;
case SystemMediaTransportControlsButton.Next:
NextClicked?.Invoke();
break;
}
});
}
public void Pause()
{
Player.Pause();
videoSource.Pause();
}
private void VideoSource_CurrentStateChanged(MediaPlayer sender, object e)
private void VideoSource_CurrentStateChanged(object sender, RoutedEventArgs e)
{
switch (Player.PlaybackSession.PlaybackState)
{
case MediaPlaybackState.Buffering:
case MediaPlaybackState.None:
case MediaPlaybackState.Opening:
case MediaPlaybackState.Paused:
systemControls.PlaybackStatus = MediaPlaybackStatus.Paused;
if (!incognito)
{
history.LeftOn = Player.PlaybackSession.Position;
HistorySet.Update(history);
}
break;
case MediaPlaybackState.Playing:
systemControls.PlaybackStatus = MediaPlaybackStatus.Playing;
break;
}
systemControls.PlaybackStatus = videoSource.CurrentState == MediaElementState.Playing ? MediaPlaybackStatus.Playing : MediaPlaybackStatus.Paused;
if(videoSource.CurrentState == MediaElementState.Paused)
if (!incognito && item.Snippet.LiveBroadcastContent == "none")
{
history.LeftOn = videoSource.Position;
HistorySet.Update(history);
}
}
private void VideoSource_MediaOpened(MediaPlayer sender, object e)
private void VideoSource_VolumeChanged(object sender, RoutedEventArgs e)
{
/*if (!needUpdateTimecode)
return;
videoSource.Position = timecodeBackup;
needUpdateTimecode = false;*/
if(videoSource.Volume != 0)
SettingsStorage.Volume = videoSource.Volume;
Controls.UpdateVolumeIcon();
}
private void VideoSource_VolumeChanged(MediaPlayer sender, object e)
{
SettingsStorage.Volume = Player.Volume;
}
private void VideoSource_MarkerReached(object sender, Windows.UI.Xaml.Media.TimelineMarkerRoutedEventArgs e)
private void VideoSource_MarkerReached(object sender, TimelineMarkerRoutedEventArgs e)
{
Controls.Advert.PushAdvert();
}
+2
View File
@@ -35,6 +35,7 @@ namespace FoxTube.Controls
Initialize(id, playlist);
}
public VideoCard(Video meta, string playlist = null)
{
InitializeComponent();
@@ -108,6 +109,7 @@ namespace FoxTube.Controls
watched.Visibility = Visibility.Visible;
if (HistorySet.Items.Exists(i => i.Id == item.Id))
{
history = HistorySet.Items.Find(i => i.Id == item.Id);
watched.Visibility = Visibility.Visible;
leftOn.Value = 100 * HistorySet.Items.Find(i => i.Id == item.Id).LeftOn.TotalSeconds / Methods.GetDuration(item.ContentDetails.Duration).TotalSeconds;
}
+2 -2
View File
@@ -430,7 +430,7 @@
<Version>10.1811.22001</Version>
</PackageReference>
<PackageReference Include="Microsoft.AppCenter.Analytics">
<Version>1.14.0</Version>
<Version>2.0.0</Version>
</PackageReference>
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
<Version>6.2.8</Version>
@@ -451,7 +451,7 @@
<Version>4.3.2</Version>
</PackageReference>
<PackageReference Include="YoutubeExplode">
<Version>4.7.0-beta</Version>
<Version>4.7.0</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>
+3 -3
View File
@@ -444,8 +444,8 @@ namespace FoxTube.Pages
private async void openBrowser_Click(object sender, RoutedEventArgs e)
{
player.Pause();
string timecode = player.Position.TotalSeconds > 10 ?
"&t=" + (int)player.Position.TotalSeconds + "s" : string.Empty;
string timecode = player.Player.Position.TotalSeconds > 10 ?
"&t=" + (int)player.Player.Position.TotalSeconds + "s" : string.Empty;
await Launcher.LaunchUriAsync($"https://www.youtube.com/watch?v={videoId}{timecode}".ToUri());
}
@@ -778,7 +778,7 @@ namespace FoxTube.Pages
private void Left_Click(object sender, RoutedEventArgs e)
{
Player.Position = history.LeftOn;
Player.Player.Position = history.LeftOn;
}
}
}
+102 -391
View File
@@ -6,9 +6,7 @@
xmlns:adverts="using:FoxTube.Controls.Adverts">
<Style TargetType="local:PlayerControls">
<Setter Property="IsTabStop" Value="False" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="FlowDirection" Value="LeftToRight" />
<Setter Property="UseSystemFocusVisuals" Value="True" />
<Setter Property="IsTextScaleFactorEnabled" Value="False" />
<Setter Property="Template">
@@ -20,132 +18,51 @@
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.Resources>
<Style x:Key="AppBarButtonStyle" TargetType="AppBarButton">
<Setter Property="Width" Value="{ThemeResource MTCMediaButtonWidth}" />
<Setter Property="Height" Value="{ThemeResource MTCMediaButtonHeight}" />
<Setter Property="AllowFocusOnInteraction" Value="True" />
</Style>
<Style x:Key="CommandBarStyle" TargetType="CommandBar">
<Setter Property="Height" Value="{ThemeResource MTCMediaButtonHeight}" />
<Setter Property="Background" Value="Transparent" />
</Style>
<Style x:Key="MediaTextBlockStyle" TargetType="TextBlock">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Foreground" Value="{ThemeResource SystemControlForegroundBaseHighBrush}" />
<Setter Property="FontSize" Value="{ThemeResource MTCMediaFontSize}" />
<Setter Property="FontFamily" Value="{ThemeResource MTCMediaFontFamily}" />
<Setter Property="Style" Value="{ThemeResource CaptionTextBlockStyle }" />
<Setter Property="IsTextScaleFactorEnabled" Value="False" />
</Style>
<Style x:Key="PlayerSeek" TargetType="Slider">
<Setter Property="Background" Value="{ThemeResource SliderTrackFill}" />
<Setter Property="BorderThickness" Value="{ThemeResource SliderBorderThemeThickness}" />
<Setter Property="Foreground" Value="{ThemeResource SliderTrackValueFill}" />
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
<Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
<Setter Property="ManipulationMode" Value="None" />
<Setter Property="UseSystemFocusVisuals" Value="True" />
<Setter Property="IsFocusEngagementEnabled" Value="True" />
<Setter Property="Margin" Value="0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Slider">
<Grid Margin="{TemplateBinding Padding}">
<Grid>
<Grid.Resources>
<Style TargetType="Thumb" x:Key="SliderThumbStyle">
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Background" Value="{ThemeResource SliderThumbBackground}" />
<Setter Property="Foreground" Value="{ThemeResource SystemControlBackgroundChromeMediumBrush}" />
<Style TargetType="Thumb">
<Setter Property="Background" Value="Red" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Thumb">
<Ellipse x:Name="ellipse"
Stroke="{TemplateBinding Background}"
StrokeThickness="2"
Fill="{TemplateBinding Foreground}" />
<Ellipse Fill="{TemplateBinding Background}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="ProgressBar" x:Key="MediaSliderProgressBarStyle">
<Setter Property="Height" Value="{ThemeResource SliderTrackThemeHeight}" />
<Setter Property="Minimum" Value="0" />
<Setter Property="Maximum" Value="100" />
<Setter Property="Foreground" Value="{ThemeResource SystemControlHighlightChromeAltLowBrush}" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="Transparent" />
<Setter Property="BorderThickness" Value="1" />
</Style>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Normal"/>
<VisualState x:Name="Pressed">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalTrackRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackFillPressed}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalTrackRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackFillPressed}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalThumb" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderThumbBackgroundPressed}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalThumb" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderThumbBackgroundPressed}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SliderContainer" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderContainerBackgroundPressed}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalDecreaseRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackValueFillPressed}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalDecreaseRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackValueFillPressed}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Disabled">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HeaderContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderHeaderForegroundDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalDecreaseRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackValueFillDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalTrackRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackFillDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalDecreaseRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackValueFillDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalTrackRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackFillDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalThumb" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderThumbBackgroundDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalThumb" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderThumbBackgroundDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="TopTickBar" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTickBarFillDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="BottomTickBar" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTickBarFillDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="LeftTickBar" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTickBarFillDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="RightTickBar" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTickBarFillDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SliderContainer" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderContainerBackgroundDisabled}" />
</ObjectAnimationUsingKeyFrames>
@@ -156,29 +73,17 @@
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalTrackRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackFillPointerOver}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalTrackRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackFillPointerOver}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalThumb" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderThumbBackgroundPointerOver}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalThumb" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderThumbBackgroundPointerOver}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SliderContainer" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderContainerBackgroundPointerOver}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalDecreaseRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackValueFillPointerOver}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalDecreaseRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackValueFillPointerOver}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="FocusEngagementStates">
<VisualState x:Name="FocusDisengaged" />
<VisualState x:Name="FocusDisengaged"/>
<VisualState x:Name="FocusEngagedHorizontal">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SliderContainer" Storyboard.TargetProperty="(Control.IsTemplateFocusTarget)">
@@ -189,156 +94,43 @@
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="FocusEngagedVertical">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SliderContainer" Storyboard.TargetProperty="(Control.IsTemplateFocusTarget)">
<DiscreteObjectKeyFrame KeyTime="0" Value="False" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalThumb" Storyboard.TargetProperty="(Control.IsTemplateFocusTarget)">
<DiscreteObjectKeyFrame KeyTime="0" Value="True" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<ContentPresenter x:Name="HeaderContentPresenter"
x:DeferLoadStrategy="Lazy"
Visibility="Collapsed"
Foreground="{ThemeResource SliderHeaderForeground}"
Margin="{ThemeResource SliderHeaderThemeMargin}"
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
FontWeight="{ThemeResource SliderHeaderThemeFontWeight}"
TextWrapping="Wrap" />
<Grid x:Name="SliderContainer"
Background="{ThemeResource SliderContainerBackground}"
Grid.Row="1"
Control.IsTemplateFocusTarget="True">
<Grid x:Name="HorizontalTemplate" MinHeight="44">
<Grid x:Name="SliderContainer" Control.IsTemplateFocusTarget="True" Background="{ThemeResource SliderContainerBackground}">
<Grid x:Name="HorizontalTemplate">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="15" />
<RowDefinition Height="Auto" />
<RowDefinition Height="15" />
</Grid.RowDefinitions>
<Rectangle x:Name="HorizontalTrackRect"
Fill="{TemplateBinding Background}"
Height="{ThemeResource SliderTrackThemeHeight}"
Grid.Row="1"
Grid.ColumnSpan="3" />
<ProgressBar x:Name="DownloadProgressIndicator"
Style="{StaticResource MediaSliderProgressBarStyle}"
Grid.Row="1"
Grid.ColumnSpan="3"
HorizontalAlignment="Stretch"
VerticalAlignment="Center" />
<Rectangle x:Name="HorizontalDecreaseRect" Fill="{TemplateBinding Foreground}" Grid.Row="1" />
<TickBar x:Name="TopTickBar"
Visibility="Collapsed"
Fill="{ThemeResource SliderTickBarFill}"
Height="{ThemeResource SliderOutsideTickBarThemeHeight}"
VerticalAlignment="Bottom"
Margin="0,0,0,4"
Grid.ColumnSpan="3" />
<TickBar x:Name="HorizontalInlineTickBar"
Visibility="Collapsed"
Fill="{ThemeResource SliderInlineTickBarFill}"
Height="{ThemeResource SliderTrackThemeHeight}"
Grid.Row="1"
Grid.ColumnSpan="3" />
<TickBar x:Name="BottomTickBar"
Visibility="Collapsed"
Fill="{ThemeResource SliderTickBarFill}"
Height="{ThemeResource SliderOutsideTickBarThemeHeight}"
VerticalAlignment="Top"
Margin="0,4,0,0"
Grid.Row="2"
Grid.ColumnSpan="3" />
<Rectangle x:Name="HorizontalTrackRect" Height="{StaticResource SliderTrackThemeHeight}" Fill="{TemplateBinding Background}" Grid.ColumnSpan="3"/>
<ProgressBar x:Name="DownloadProgressIndicator" Grid.ColumnSpan="3"
Height="{ThemeResource SliderTrackThemeHeight}"
Foreground="{ThemeResource SystemControlHighlightChromeAltLowBrush}"
Background="Transparent"
BorderBrush="Transparent"
BorderThickness="1"/>
<Rectangle Height="4" x:Name="HorizontalDecreaseRect" Fill="{StaticResource SystemAccentColor}"/>
<Thumb x:Name="HorizontalThumb"
Style="{StaticResource SliderThumbStyle}"
Height="15"
Width="15"
Grid.Row="0"
Grid.RowSpan="3"
Grid.Column="1"
AutomationProperties.AccessibilityView="Raw">
<ToolTipService.ToolTip>
<ToolTip x:Name="ThumbnailTooltip">
<ContentPresenter Content="{Binding}" />
</ToolTip>
</ToolTipService.ToolTip>
Height="15" Width="15" Grid.Column="1"
AutomationProperties.AccessibilityView="Raw">
<Thumb.DataContext>
<Grid Height="112" Width="192">
<Image x:Name="ThumbnailImage"/>
<Border Background="{ThemeResource SystemControlBackgroundBaseMediumBrush}"
VerticalAlignment="Bottom"
HorizontalAlignment="Left">
<TextBlock x:Name="TimeElapsedPreview"
Margin="6,1,6,3"
Style="{StaticResource BodyTextBlockStyle}"
IsTextScaleFactorEnabled="False"
Foreground="{ThemeResource SystemControlPageTextBaseMediumBrush}" />
</Border>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="112"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Image x:Name="ThumbnailImage" Width="192"/>
<TextBlock Grid.Row="1" x:Name="TimeElapsedPreview"/>
</Grid>
</Thumb.DataContext>
</Thumb>
</Grid>
<Grid x:Name="VerticalTemplate" MinWidth="44" Visibility="Collapsed">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="18" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="18" />
</Grid.ColumnDefinitions>
<Rectangle x:Name="VerticalTrackRect"
Fill="{TemplateBinding Background}"
Width="{ThemeResource SliderTrackThemeHeight}"
Grid.Column="1"
Grid.RowSpan="3" />
<Rectangle x:Name="VerticalDecreaseRect"
Fill="{TemplateBinding Foreground}"
Grid.Column="1"
Grid.Row="2" />
<TickBar x:Name="LeftTickBar"
Visibility="Collapsed"
Fill="{ThemeResource SliderTickBarFill}"
Width="{ThemeResource SliderOutsideTickBarThemeHeight}"
HorizontalAlignment="Right"
Margin="0,0,4,0"
Grid.RowSpan="3" />
<TickBar x:Name="VerticalInlineTickBar"
Visibility="Collapsed"
Fill="{ThemeResource SliderInlineTickBarFill}"
Width="{ThemeResource SliderTrackThemeHeight}"
Grid.Column="1"
Grid.RowSpan="3" />
<TickBar x:Name="RightTickBar"
Visibility="Collapsed"
Fill="{ThemeResource SliderTickBarFill}"
Width="{ThemeResource SliderOutsideTickBarThemeHeight}"
HorizontalAlignment="Left"
Margin="4,0,0,0"
Grid.Column="2"
Grid.RowSpan="3" />
<Thumb x:Name="VerticalThumb"
Style="{StaticResource SliderThumbStyle}"
DataContext="{TemplateBinding Value}"
Width="24"
Height="8"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="3"
FocusVisualMargin="-6,-14,-6,-14"
AutomationProperties.AccessibilityView="Raw" />
</Grid>
</Grid>
</Grid>
</ControlTemplate>
@@ -348,7 +140,6 @@
</Grid.Resources>
<VisualStateManager.VisualStateGroups>
<!-- ControlPanel Visibility states -->
<VisualStateGroup x:Name="ControlPanelVisibilityStates">
<VisualState x:Name="ControlPanelFadeIn">
<Storyboard>
@@ -356,66 +147,34 @@
<EasingDoubleKeyFrame KeyTime="0" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="1" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity" Storyboard.TargetName="center">
<EasingDoubleKeyFrame KeyTime="0" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="1" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity" Storyboard.TargetName="centerBgr">
<EasingDoubleKeyFrame KeyTime="0" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="1" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimation Storyboard.TargetProperty="Y" Storyboard.TargetName="TranslateVerticalBottom" From="50" To ="0" Duration="0:0:0.3"/>
<DoubleAnimation Storyboard.TargetProperty="Y" Storyboard.TargetName="TranslateVerticalTop" From="-50" To ="0" Duration="0:0:0.3"/>
</Storyboard>
</VisualState>
<VisualState x:Name="ControlPanelFadeOut">
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity" Storyboard.TargetName="Border">
<EasingDoubleKeyFrame KeyTime="0" Value="1" />
<EasingDoubleKeyFrame KeyTime="0:0:0.7" Value="0" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity" Storyboard.TargetName="center">
<EasingDoubleKeyFrame KeyTime="0" Value="1" />
<EasingDoubleKeyFrame KeyTime="0:0:0.7" Value="0" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity" Storyboard.TargetName="centerBgr">
<EasingDoubleKeyFrame KeyTime="0" Value="1" />
<EasingDoubleKeyFrame KeyTime="0:0:0.7" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="0" />
</DoubleAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="IsHitTestVisible" Storyboard.TargetName="Border">
<DiscreteObjectKeyFrame KeyTime="0" Value="False" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="IsHitTestVisible" Storyboard.TargetName="center">
<DiscreteObjectKeyFrame KeyTime="0" Value="False" />
</ObjectAnimationUsingKeyFrames>
<DoubleAnimation Storyboard.TargetProperty="Y" Storyboard.TargetName="TranslateVerticalBottom" From="0" To ="50" Duration="0:0:0.7"/>
<DoubleAnimation Storyboard.TargetProperty="Y" Storyboard.TargetName="TranslateVerticalTop" From="0" To ="-50" Duration="0:0:0.7"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
<!-- ControlPanel Visibility states -->
<!-- Media state visual states -->
<VisualStateGroup x:Name="MediaStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Buffering">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="BufferingProgressBar">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Loading">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="BufferingProgressBar">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
</ObjectAnimationUsingKeyFrames>
<DoubleAnimation Storyboard.TargetName="ProgressSlider"
Storyboard.TargetProperty="Opacity"
@@ -435,28 +194,10 @@
</Storyboard>
</VisualState>-->
<VisualState x:Name="Disabled">
<Storyboard />
<Storyboard/>
</VisualState>
</VisualStateGroup>
<!-- Focus states -->
<VisualStateGroup x:Name="FocusStates">
<VisualState x:Name="Focused">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="FocusVisualWhite"
Storyboard.TargetProperty="Opacity"
To="1"
Duration="0" />
<DoubleAnimation Storyboard.TargetName="FocusVisualBlack"
Storyboard.TargetProperty="Opacity"
To="1"
Duration="0" />
</Storyboard>
</VisualState>
<VisualState x:Name="Unfocused" />
<VisualState x:Name="PointerFocused" />
</VisualStateGroup>
<VisualStateGroup x:Name="PlayPauseStates">
<VisualState x:Name="PlayState" />
<VisualState x:Name="PauseState">
@@ -467,7 +208,6 @@
</Storyboard>
</VisualState>
</VisualStateGroup>
<!-- FullWindow states -->
<VisualStateGroup x:Name="FullWindowStates">
<VisualState x:Name="NonFullWindowState" />
<VisualState x:Name="FullWindowState">
@@ -481,161 +221,132 @@
</VisualStateManager.VisualStateGroups>
<Border x:Name="Border">
<Border.Resources>
<Style TargetType="Button" BasedOn="{StaticResource ButtonRevealStyle}">
<Setter Property="Width" Value="50"/>
<Setter Property="Height" Value="50"/>
<Setter Property="FontSize" Value="20"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="FontFamily" Value="Segoe UI, Segoe MDL2 Assets"/>
</Style>
</Border.Resources>
<Grid x:Name="ControlPanelGrid">
<Grid.Resources>
<Style TargetType="Button" BasedOn="{StaticResource ButtonRevealStyle}">
<Setter Property="Width" Value="50"/>
<Setter Property="Height" Value="50"/>
<Setter Property="Background" Value="Transparent"/>
</Style>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition/>
<RowDefinition Height="auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid x:Name="header">
<Grid.RenderTransform>
<TranslateTransform x:Name="TranslateVerticalTop"/>
</Grid.RenderTransform>
<Grid.Background>
<AcrylicBrush TintColor="#CC000000" TintOpacity=".6"/>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="Black"/>
<GradientStop Color="#00000000" Offset="1"/>
</LinearGradientBrush>
</Grid.Background>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Button x:Name="minimize">
<FontIcon Glyph="&#xE011;"/>
</Button>
<Button x:Name="MinimizeButton" HorizontalAlignment="Left" VerticalAlignment="Top" Content="&#xE011;"/>
<StackPanel Margin="10,0" Grid.Column="1" VerticalAlignment="Center">
<TextBlock x:Name="title" TextTrimming="CharacterEllipsis" FontSize="20" MaxLines="1"/>
<TextBlock Foreground="LightGray" FontStyle="Italic" x:Name="author"/>
<StackPanel VerticalAlignment="Top" Grid.Column="1" Margin="10,0">
<TextBlock x:Name="title" Text="Name" TextTrimming="CharacterEllipsis" MaxLines="1" Style="{StaticResource TitleTextBlockStyle}"/>
<TextBlock x:Name="channel" Text="Channel" TextTrimming="CharacterEllipsis" FontStyle="Italic"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Grid.Column="2" x:Name="headerToolbar">
<Button x:Name="close">
<FontIcon Glyph="&#xE106;"/>
</Button>
<Button x:Name="CastButton">
<FontIcon Glyph="&#xEC15;"/>
</Button>
<Button x:Name="CompactOverlayButton" VerticalAlignment="Top" HorizontalAlignment="Right">
<FontIcon Glyph="&#xE2B3;"/>
</Button>
<StackPanel x:Name="RightHeaderControls" Orientation="Horizontal" VerticalAlignment="Top" Grid.Column="2">
<Button x:Name="CloseButton" HorizontalAlignment="Right" VerticalAlignment="Top" Content="&#xE106;"/>
<Button x:Name="CastButton" Content="&#xEC15;"/>
<Button x:Name="CompactOverlayButton" HorizontalAlignment="Right" VerticalAlignment="Top" Content="&#xE2B3;"/>
</StackPanel>
</Grid>
<Grid Grid.Row="1" x:Name="centerBgr" Visibility="Collapsed" IsHitTestVisible="False" Background="#66000000"/>
<Grid Grid.Row="1" x:Name="center" Visibility="Collapsed">
<Button x:Name="maximize" VerticalAlignment="Top" HorizontalAlignment="Left" IsHitTestVisible="True">
<FontIcon Glyph="&#xE010;"/>
</Button>
<Button x:Name="compactClose" VerticalAlignment="Top" HorizontalAlignment="Right">
<FontIcon Glyph="&#xE106;"/>
</Button>
<Button Height="32" Width="50" Margin="0,0,48,0" VerticalAlignment="Top" HorizontalAlignment="Right" Visibility="Collapsed" FontFamily="Segoe MDL2 Assets" Content="&#xE700;" IsHitTestVisible="False" x:Name="dragholder"/>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center" x:Name="centralStack"/>
<ProgressBar VerticalAlignment="Bottom" x:Name="compactSeek" Background="Transparent"/>
<Grid x:Name="center" Grid.Row="1" Visibility="Collapsed" Background="#7F000000">
<Button x:Name="drag" IsHitTestVisible="False" Height="32" Width="47" Margin="0,0,47,0" Content="&#xE700;" Visibility="Collapsed" Padding="0" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" VerticalAlignment="Top" HorizontalAlignment="Right"/>
<StackPanel x:Name="centerControls" Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<ProgressBar x:Name="SeekIndicator" Background="Transparent" VerticalAlignment="Bottom"/>
</Grid>
<Grid Grid.Row="2" x:Name="footer">
<Grid.RenderTransform>
<TranslateTransform x:Name="TranslateVerticalBottom"/>
</Grid.RenderTransform>
<Grid x:Name="footer" Grid.Row="2">
<Grid.Background>
<AcrylicBrush TintColor="#CC000000" TintOpacity=".6"/>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="Black" Offset="1"/>
<GradientStop Color="#00000000" Offset="0"/>
</LinearGradientBrush>
</Grid.Background>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" x:Name="leftStack">
<ProgressBar x:Name="BufferingProgressBar" VerticalAlignment="Bottom" Grid.ColumnSpan="3" Background="Transparent" IsIndeterminate="True" Visibility="Collapsed"/>
<StackPanel x:Name="LeftFooterControls" Orientation="Horizontal" VerticalAlignment="Bottom">
<Button x:Name="PlayPauseButton">
<SymbolIcon x:Name="PlayPauseSymbol" Symbol="Play"/>
</Button>
<Button x:Name="next">
<SymbolIcon Symbol="Next"/>
</Button>
<Button x:Name="volume">
<FontIcon Glyph="&#xE15D;"/>
<Button x:Name="NextButton" Content="&#xE101;"/>
<Button x:Name="VolumeMenuButton" Content="&#xE15D;">
<Button.Flyout>
<Flyout>
<StackPanel Orientation="Horizontal" Margin="-10">
<Button x:Name="AudioMuteButton" Width="50" Height="50" Background="Transparent" FontFamily="Segoe MDL2 Assets" FontSize="25">
<FontIcon Glyph="&#xE15D;"/>
</Button>
<Slider Foreground="Red" Orientation="Horizontal" Width="150" Margin="10,5,10,0" VerticalAlignment="Center" x:Name="VolumeSlider"/>
<Button x:Name="AudioMuteButton" Content="&#xE15D;" FontFamily="Segoe MDL2 Assets" Height="50" Width="50" Background="Transparent" FontSize="20"/>
<Slider x:Name="VolumeSlider" Width="150" Margin="10,5,10,0" VerticalAlignment="Center" TickPlacement="Outside" TickFrequency="10"/>
</StackPanel>
</Flyout>
</Button.Flyout>
</Button>
<Button x:Name="goLive" Width="NaN" Visibility="Collapsed">
<Button x:Name="PlayLiveButton" Width="NaN" Visibility="Collapsed">
<TextBlock x:Uid="/VideoPage/live" Text="🔴 LIVE"/>
</Button>
</StackPanel>
<Grid Grid.Column="1" Margin="10,5" x:Name="sliderPan">
<TextBlock VerticalAlignment="Bottom" HorizontalAlignment="Left" x:Name="TimeElapsedElement" Text="00:00"/>
<TextBlock VerticalAlignment="Bottom" HorizontalAlignment="Right" x:Name="TimeRemainingElement" Text="00:00"/>
<Grid VerticalAlignment="Top" Height="4" Margin="0,15,0,0">
<ProgressBar Background="#66FFFFFF" Foreground="#66FFFFFF" x:Name="BufferingProgressBar"/>
</Grid>
<Slider x:Name="ProgressSlider" Style="{StaticResource PlayerSeek}" IsThumbToolTipEnabled="False" Background="Transparent" HorizontalAlignment="Stretch" VerticalAlignment="Top"/>
<Grid x:Name="CenterFooterControls" Height="50" Grid.Column="1" VerticalAlignment="Bottom">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="1" Margin="5,0" VerticalAlignment="Center" HorizontalAlignment="Left" x:Name="TimeElapsedElement" Text="00:00"/>
<TextBlock Grid.Row="1" Margin="5,0" VerticalAlignment="Center" HorizontalAlignment="Right" x:Name="TimeRemainingElement" Text="00:00"/>
<Slider x:Name="ProgressSlider" Style="{StaticResource PlayerSeek}" IsThumbToolTipEnabled="False" HorizontalAlignment="Stretch" VerticalAlignment="Center" Margin="5,0"/>
</Grid>
<StackPanel Orientation="Horizontal" Grid.Column="2" x:Name="rightStack">
<Button x:Name="SkipBackwardButton">
<FontIcon Glyph="&#xED3C;"/>
</Button>
<Button x:Name="SkipForwardButton">
<FontIcon Glyph="&#xED3D;"/>
</Button>
<StackPanel x:Name="RightFooterControls" Grid.Column="2" VerticalAlignment="Bottom" Orientation="Horizontal">
<Button x:Name="SkipBackwardButton" Content="&#xED3C;"/>
<Button x:Name="SkipForwardButton" Content="&#xED3D;"/>
<Line Stroke="White" StrokeThickness="2" Y1="5" Y2="45"/>
<Button x:Name="cc">
<SymbolIcon Symbol="ClosedCaption"/>
<Button x:Name="CaptionsMenuButton" Content="&#xE7F0;">
<Button.Flyout>
<Flyout>
<StackPanel Width="225">
<ToggleSwitch x:Name="ccSwitch" OnContent="Subtitles" OffContent="Subtitles" x:Uid="/VideoPage/subsSwitch"/>
<ComboBox x:Name="ccSelector" Header="Language" x:Uid="/VideoPage/subsSelector" PlaceholderText="No subtitles are available" HorizontalAlignment="Stretch"/>
<ToggleSwitch x:Name="CaptionsToggleSwitch" OnContent="Subtitles" OffContent="Subtitles" x:Uid="/VideoPage/subsSwitch"/>
<ComboBox x:Name="CaptionsSelector" Header="Language" x:Uid="/VideoPage/subsSelector" PlaceholderText="No captions are available" HorizontalAlignment="Stretch"/>
</StackPanel>
</Flyout>
</Button.Flyout>
</Button>
<Button x:Name="quality">
<SymbolIcon Symbol="Setting"/>
<Button x:Name="QualityMenuButton" Content="&#xE115;">
<Button.Flyout>
<Flyout>
<ComboBox Width="225" x:Uid="/VideoPage/qualitySelector" Header="Quality" x:Name="qualitySelector"/>
<ComboBox Width="225" x:Name="QualitySelector" Header="Language" x:Uid="/VideoPage/qualitySelector"/>
</Flyout>
</Button.Flyout>
</Button>
<Button x:Name="FullWindowButton">
<SymbolIcon x:Name="FullWindowSymbol" Symbol="FullScreen"/>
</Button>
</StackPanel>
</Grid>
</Grid>
</Border>
<controls:LiveCaptions Visibility="Collapsed" x:Name="captions"/>
<adverts:PlayerAdvert Grid.Row="1" x:Name="ad" VerticalAlignment="Bottom"/>
<controls:LiveCaptions Visibility="Collapsed" x:Name="CaptionControl"/>
<adverts:PlayerAdvert Grid.Row="1" x:Name="AdvertControl" VerticalAlignment="Bottom"/>
</Grid>
</ControlTemplate>
</Setter.Value>