diff --git a/FoxTube/Classes/Caption.cs b/FoxTube/Classes/Caption.cs index 14567bc..f971aa1 100644 --- a/FoxTube/Classes/Caption.cs +++ b/FoxTube/Classes/Caption.cs @@ -22,5 +22,12 @@ namespace FoxTube.Classes Duration = TimeSpan.FromMilliseconds(duration); Text = text; } + + public Caption(double startTime, double duration, string text) + { + Start = TimeSpan.FromSeconds(startTime); + Duration = TimeSpan.FromSeconds(duration); + Text = text; + } } } diff --git a/FoxTube/Classes/DownloadAgent.cs b/FoxTube/Classes/DownloadAgent.cs index 6c4748c..af7a334 100644 --- a/FoxTube/Classes/DownloadAgent.cs +++ b/FoxTube/Classes/DownloadAgent.cs @@ -1,68 +1,72 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Xml; using System.IO; -using MyToolkit.Multimedia; using Windows.Storage; +using FoxTube.Classes; +using Newtonsoft.Json; +using Windows.UI.Popups; namespace FoxTube.Controls { public class DownloadAgent { - public List Items = new List(); - public event ObjectEventHandler ListChanged; - - private ApplicationDataContainer settings = ApplicationData.Current.LocalSettings; - XmlDocument doc = new XmlDocument(); + public List items = new List(); + StorageFolder roaming = ApplicationData.Current.RoamingFolder; + public DownloadAgent() { - if (settings.Values["downloadHistory"] != null) - doc.LoadXml(settings.Values["downloadHistory"] as string); - else + Initialize(); + Windows.UI.Core.Preview.SystemNavigationManagerPreview.GetForCurrentView().CloseRequested += (s, a) => QuitPrompt(); + } + + public async void Initialize() + { + try { - doc.AppendChild(doc.CreateXmlDeclaration("1.0", "utf-8", null)); - doc.AppendChild(doc.CreateElement("downloads")); - settings.Values.Add("downloadHistory", doc.InnerXml); + List containers = JsonConvert.DeserializeObject>(await FileIO.ReadTextAsync(await roaming.GetFileAsync("data.json"))); + + foreach (DownloadItemContainer i in containers) + try { items.Add(new DownloadItem(i)); } + catch (FileNotFoundException) { } } - - foreach(XmlElement e in doc["downloads"].ChildNodes) - try - { - Items.Add(new DownloadItem( - e["details"]["id"].InnerText, - e["title"].InnerText, - e["snippet"]["author"].InnerText, - e["snippet"]["image"].InnerText, - e["snippet"]["duration"].InnerText, - e["snippet"]["quality"].InnerText, - e["details"]["path"].InnerText)); - } - catch { } + catch { } } - public void Add(string id, YouTubeQuality quality) + public void Add(string url) { - DownloadItem item = new DownloadItem(id, quality); - item.DownloadCanceled += Item_DownloadCanceled; - item.DownloadComplete += Item_DownloadComplete; - - Items.Add(item); - ListChanged.Invoke(item, "add"); - } - - private void Item_DownloadComplete(object sender, params object[] e) - { - doc["downloads"].InnerXml += e[0]; - settings.Values["downloadHistory"] = doc.InnerXml; + items.Add(new DownloadItem(url)); } private void Item_DownloadCanceled(object sender, params object[] e) { - Items.Remove(sender as DownloadItem); - ListChanged.Invoke(sender, "remove"); + items.Remove(sender as DownloadItem); + } + + public async void QuitPrompt() + { + if(items.Find(x => x.InProgress) != null) + { + MessageDialog dialog = new MessageDialog("You have some unfinished downloads. Quitting now will erase all downloaded data. Are you sure to continue?", "Some downloads are still pending"); + + dialog.Commands.Add(new UICommand("Yes", async (command) => + { + foreach (DownloadItem i in items.FindAll(x => x.InProgress)) + i.Cancel(); + items.RemoveAll(x => x.InProgress); + + List containers = new List(); + foreach (DownloadItem i in items) + containers.Add(i.Container); + + await FileIO.WriteTextAsync( + await roaming.CreateFileAsync("data.json", CreationCollisionOption.ReplaceExisting), + JsonConvert.SerializeObject(containers)); + })); + dialog.Commands.Add(new UICommand("No")); + + dialog.DefaultCommandIndex = 1; + await dialog.ShowAsync(); + } } } } diff --git a/FoxTube/Classes/DownloadItemContainer.cs b/FoxTube/Classes/DownloadItemContainer.cs new file mode 100644 index 0000000..f7e6fe2 --- /dev/null +++ b/FoxTube/Classes/DownloadItemContainer.cs @@ -0,0 +1,16 @@ +using System; +using YoutubeExplode.Models.MediaStreams; + +namespace FoxTube.Classes +{ + public class DownloadItemContainer + { + public string Title { get; set; } + public string Channel { get; set; } + public string Id { get; set; } + public Uri Path { get; set; } + public Uri Thumbnail { get; set; } + public VideoQuality Quality { get; set; } + public TimeSpan Duration { get; set; } + } +} diff --git a/FoxTube/Classes/Methods.cs b/FoxTube/Classes/Methods.cs index 2913870..f7fa11e 100644 --- a/FoxTube/Classes/Methods.cs +++ b/FoxTube/Classes/Methods.cs @@ -1,9 +1,10 @@ -using MyToolkit.Multimedia; +using Google.Apis.YouTube.v3; using System; using System.Diagnostics; using System.Text.RegularExpressions; -using System.Threading.Tasks; +using System.Web; using Windows.ApplicationModel.Core; +using Windows.System; using Windows.UI; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; @@ -14,6 +15,7 @@ namespace FoxTube { public static class Methods { + public static bool NeedToResponse { get; set; } = false; public static MainPage MainPage { get { return (Window.Current.Content as Frame).Content as MainPage; } @@ -24,6 +26,11 @@ namespace FoxTube CoreApplication.Exit(); } + public static Uri ToUri(this string url) + { + return new Uri(url); + } + public static string GetAgo(DateTime dateTime) { TimeSpan span = DateTime.Now - dateTime; @@ -69,7 +76,8 @@ namespace FoxTube { try { - Hyperlink link = new Hyperlink { NavigateUri = new Uri(item), Foreground = new SolidColorBrush(Colors.Red) }; + Hyperlink link = new Hyperlink(); + link.Click += (s, arg) => { ProcessLink(item); }; link.Inlines.Add(new Run { Text = item }); block.Inlines.Add(link); } @@ -98,7 +106,7 @@ namespace FoxTube } } - public static string QualityToString(YouTubeQuality quality) + /*public static string QualityToString(YouTubeQuality quality) { switch(quality) { @@ -131,6 +139,70 @@ namespace FoxTube default: return "Unknown"; } + }*/ + + public async static void ProcessLink(string url) + { + try + { + Debug.WriteLine($"Processing link: {url}"); + if (url.Contains("youtube.com/") || url.Contains("youtu.be/")) + { + Debug.WriteLine("This is an internal youtube link"); + url = url.Replace("https://", "").Replace("http://", "").Replace("wwww.", "").Replace("//", ""); + Debug.WriteLine($"Prepared link: {url}"); + + if (url.Contains("/playlist")) + { + Debug.WriteLine($"This is a playlist link. ID: {HttpUtility.ParseQueryString(url).Get("list")}"); + MainPage.GoToPlaylist(HttpUtility.ParseQueryString(url).Get("list")); + } + else if (url.Contains("youtu.be/")) + { + Debug.WriteLine($"This is obfuscated video link. Video ID: {url.Split('/')[1]}"); + MainPage.GoToVideo(url.Split('/')[1]); + } + else if (url.Contains("/watch")) + { + Debug.WriteLine($"This is regular video link. Video ID: {HttpUtility.ParseQueryString(url).Get("v")}"); + MainPage.GoToVideo(HttpUtility.ParseQueryString(url).Get(0), HttpUtility.ParseQueryString(url).Get("list")); + } + else if (url.Contains("/v/")) + { + Debug.WriteLine($"This is video link. ID: {url.Split('/')[2].Split('?')[0]}"); + MainPage.GoToVideo(url.Split('/')[2].Split('?')[0]); + } + else if (url.Contains("/channel/")) + { + Debug.WriteLine($"This is channel link. ID: {url.Split('/')[2]}"); + MainPage.GoToChannel(url.Split('/')[2]); + } + else if (url.Contains("/user/")) + { + Debug.WriteLine($"This is channel link with username. Username: {url.Split('/')[2]}"); + ChannelsResource.ListRequest request = SecretsVault.Service.Channels.List("id"); + Debug.WriteLine(request.ForUsername = url.Split('/')[2]); + request.MaxResults = 1; + MainPage.GoToChannel((await request.ExecuteAsync()).Items[0].Id); + } + else if (url.Contains("/c/")) + { + Debug.WriteLine($"This is channel link with custom url. Custom name: {url.Split('/')[2]}"); + SearchResource.ListRequest request = SecretsVault.Service.Search.List("id"); + Debug.WriteLine(request.Q = url.Split('/')[2]); + request.MaxResults = 1; + MainPage.GoToChannel((await request.ExecuteAsync()).Items[0].Id.ChannelId); + } + else + throw new Exception(); + } + else + throw new Exception(); + } + catch + { + await Launcher.LaunchUriAsync(new Uri(url)); + } } } } diff --git a/FoxTube/Classes/ObjectEventArgs.cs b/FoxTube/Classes/ObjectEventArgs.cs index db18307..23cd9d0 100644 --- a/FoxTube/Classes/ObjectEventArgs.cs +++ b/FoxTube/Classes/ObjectEventArgs.cs @@ -121,7 +121,6 @@ namespace FoxTube public string Channel { get; private set; } public Filters Filter { get; private set; } = new Filters(); - public SearchParameters(string term) { Term = term; diff --git a/FoxTube/Classes/SecretsVault.cs b/FoxTube/Classes/SecretsVault.cs index 4be5580..2d305b5 100644 --- a/FoxTube/Classes/SecretsVault.cs +++ b/FoxTube/Classes/SecretsVault.cs @@ -57,6 +57,45 @@ namespace FoxTube } } + public static async Task ChangeSubscriptionState(string id) + { + if (!IsAuthorized) + return false; + + if(Subscriptions.Find(x => x.Snippet.ResourceId.ChannelId == id) != null) + { + Subscription s = Subscriptions.Find(x => x.Snippet.ResourceId.ChannelId == id); + + try { await Service.Subscriptions.Delete(s.Id).ExecuteAsync(); } + catch { return true; } + + SubscriptionsChanged.Invoke(null, "remove", Subscriptions.IndexOf(s)); + Subscriptions.Remove(s); + return false; + } + else + { + var request = Service.Subscriptions.Insert(new Subscription() + { + Snippet = new SubscriptionSnippet() + { + ResourceId = new ResourceId() + { + ChannelId = id, + Kind = "youtube#channel" + } + } + }, "snippet"); + + Subscription s = await request.ExecuteAsync(); + if (s == null) + return false; + Subscriptions.Add(s); + SubscriptionsChanged.Invoke(null, "add", s); + return true; + } + } + public static async Task Subscribe(string id) { if (!IsAuthorized) @@ -75,6 +114,8 @@ namespace FoxTube }, "snippet"); Subscription s = await request.ExecuteAsync(); + if (s == null) + return false; Subscriptions.Add(s); SubscriptionsChanged.Invoke(null, "add", s); return true; diff --git a/FoxTube/Controls/ChannelCard.xaml.cs b/FoxTube/Controls/ChannelCard.xaml.cs index 3da8933..7ccd966 100644 --- a/FoxTube/Controls/ChannelCard.xaml.cs +++ b/FoxTube/Controls/ChannelCard.xaml.cs @@ -91,23 +91,17 @@ namespace FoxTube.Controls private async void subscribe_Click(object sender, RoutedEventArgs e) { - if (subscribe.Background == new SolidColorBrush(Colors.Red)) + if (await SecretsVault.ChangeSubscriptionState(channelId)) { - if (await SecretsVault.Subscribe(channelId)) - { - subscribe.Background = new SolidColorBrush(Colors.Transparent); - subscribe.Foreground = new SolidColorBrush(Colors.Gray); - subscribe.Content = "Subscribed"; - } + subscribe.Background = new SolidColorBrush(Colors.Transparent); + subscribe.Foreground = new SolidColorBrush(Colors.Gray); + subscribe.Content = "Subscribed"; } else { - if (await SecretsVault.Unsubscibe(channelId)) - { - subscribe.Background = new SolidColorBrush(Colors.Red); - subscribe.Foreground = new SolidColorBrush(Colors.White); - subscribe.Content = "Subscribe"; - } + subscribe.Background = new SolidColorBrush(Colors.Red); + subscribe.Foreground = new SolidColorBrush(Colors.White); + subscribe.Content = "Subscribe"; } } } diff --git a/FoxTube/Controls/CommentCard.xaml b/FoxTube/Controls/CommentCard.xaml index 65d47ae..d1d0174 100644 --- a/FoxTube/Controls/CommentCard.xaml +++ b/FoxTube/Controls/CommentCard.xaml @@ -23,15 +23,20 @@ + - - + + + + + + - - + + + - diff --git a/FoxTube/Controls/VideoPlayer.xaml.cs b/FoxTube/Controls/VideoPlayer.xaml.cs index b769eb6..e8eeefa 100644 --- a/FoxTube/Controls/VideoPlayer.xaml.cs +++ b/FoxTube/Controls/VideoPlayer.xaml.cs @@ -1,26 +1,17 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Runtime.InteropServices.WindowsRuntime; using System.Timers; using Windows.Foundation; -using Windows.Foundation.Collections; using Windows.Storage; using Windows.UI.Core; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls.Primitives; -using Windows.UI.Xaml.Data; using Windows.UI.Xaml.Input; using Windows.UI.Xaml.Media; -using Windows.UI.Xaml.Navigation; -using Microsoft.Toolkit.Uwp.UI.Animations; - using Google.Apis.YouTube.v3.Data; -using Google.Apis.YouTube.v3; using Windows.UI.Xaml.Media.Imaging; -using Windows.Networking.Connectivity; using System.Diagnostics; using Windows.Media; using Windows.Storage.Streams; @@ -28,13 +19,13 @@ using Windows.UI.ViewManagement; using System.Xml; using Windows.ApplicationModel.Core; using Windows.UI; -using Windows.Graphics.Display; using Windows.Media.Casting; using YoutubeExplode.Models.MediaStreams; using YoutubeExplode; using YoutubeExplode.Models.ClosedCaptions; using System.Globalization; using FoxTube.Controls; +using Windows.System; // The User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236 @@ -44,14 +35,31 @@ namespace FoxTube { public string videoId; - public bool miniView = false; + private bool miniview = false; + public bool MiniView + { + get { return miniview; } + set + { + miniview = value; + if (value) + captions.Hide(); + else + captions.Show(); + } + } private bool fullScreen = false; public bool pointerCaptured = false; public event ObjectEventHandler SetFullSize; public event ObjectEventHandler NextClicked; + bool isMuxed = false; + bool audioReady = false; + bool videoReady = false; + CoreCursor cursorBackup = Window.Current.CoreWindow.PointerCursor; + Point cursorPositionBackup; public TimeSpan elapsed; TimeSpan remaining; @@ -65,7 +73,6 @@ namespace FoxTube SystemMediaTransportControls systemControls = SystemMediaTransportControls.GetForCurrentView(); LiveCaptions captions; - YoutubeClient client = new YoutubeClient(); IReadOnlyList ccInfo; MediaStreamInfoSet streamInfo; @@ -86,13 +93,20 @@ namespace FoxTube this.InitializeComponent(); Visibility = Visibility.Collapsed; if (!ApplicationView.GetForCurrentView().IsViewModeSupported(ApplicationViewMode.CompactOverlay)) - miniViewBtn.Visibility = Visibility.Collapsed;; + miniViewBtn.Visibility = Visibility.Collapsed; - captions = grid.Children[1] as LiveCaptions; + captions = grid.Children[2] as LiveCaptions; volume.Value = Convert.ToDouble(settings.Values["volume"]); videoSource.AutoPlay = (bool)settings.Values["videoAutoplay"]; + videoSource.MediaEnded += (s, arg) => + { + seek.Value = seek.Maximum; + seekIndicator.Value = seekIndicator.Maximum; + seekTimer.Stop(); + }; + audioSource.CurrentStateChanged += AudioSource_CurrentStateChanged; t.Elapsed += T_Elapsed; seekTimer.Elapsed += SeekTimer_Elapsed; } @@ -105,39 +119,36 @@ namespace FoxTube #region Retrieving info for CC and Media streams //Loading streams - streamInfo = await client.GetVideoMediaStreamInfosAsync(item.Id); - foreach(MuxedStreamInfo i in streamInfo.Muxed) - { - if (i.VideoQuality == VideoQuality.High2160) - (quality.Items[0] as ComboBoxItem).Visibility = Visibility.Visible; - if (i.VideoQuality == VideoQuality.High1080) - (quality.Items[1] as ComboBoxItem).Visibility = Visibility.Visible; - if (i.VideoQuality == VideoQuality.High720) - (quality.Items[2] as ComboBoxItem).Visibility = Visibility.Visible; - if (i.VideoQuality == VideoQuality.Medium480) - (quality.Items[3] as ComboBoxItem).Visibility = Visibility.Visible; - if (i.VideoQuality == VideoQuality.Medium360) - (quality.Items[4] as ComboBoxItem).Visibility = Visibility.Visible; - if (i.VideoQuality == VideoQuality.Low240) - (quality.Items[5] as ComboBoxItem).Visibility = Visibility.Visible; - if (i.VideoQuality == VideoQuality.Low144) - (quality.Items[6] as ComboBoxItem).Visibility = Visibility.Visible; - } + streamInfo = await new YoutubeClient().GetVideoMediaStreamInfosAsync(videoId); + streamInfo.Audio.ToList().ForEach(x => Debug.WriteLine($"{x.AudioEncoding} {x.Bitrate}")); - int k = (int)settings.Values["quality"]; - if ((quality.Items[k] as ComboBoxItem).Visibility == Visibility.Visible) - quality.SelectedIndex = k; + List q = streamInfo.GetAllVideoQualities().ToList(); + q.Sort(); + q.Reverse(); + foreach (VideoQuality i in q) + quality.Items.Add(new ComboBoxItem() { Content = i.GetVideoQualityLabel() }); + + string s; + if ((string)settings.Values["quality"] == "remember") + s = (string)settings.Values["rememberedQuality"]; else - quality.SelectedItem = quality.Items.First(x => (x as ComboBoxItem).Visibility == Visibility.Visible); + s = (string)settings.Values["quality"]; + + if (quality.Items.ToList().Find(x => (x as ComboBoxItem).Content as string == s) != null) + quality.SelectedItem = quality.Items.First(x => (x as ComboBoxItem).Content as string == s); + else + quality.SelectedItem = quality.Items.First(); //Loading captions - ccInfo = await client.GetVideoClosedCaptionTrackInfosAsync(item.Id); + ccInfo = await new YoutubeClient().GetVideoClosedCaptionTrackInfosAsync(item.Id); foreach (ClosedCaptionTrackInfo cc in ccInfo) + { subsLang.Items.Add(new ComboBoxItem() { - Content = string.Format("{1}{0}", CultureInfo.GetCultureInfo(cc.Language.Code).DisplayName, cc.IsAutoGenerated ? " (Auto-generated)" : ""), + Content = string.Format("{0}{1}", CultureInfo.GetCultureInfo(cc.Language.Code).DisplayName, cc.IsAutoGenerated ? " (Auto-generated)" : ""), Tag = cc }); + } if(ccInfo.Count > 0) subsLang.SelectedIndex = 0; else @@ -232,22 +243,30 @@ namespace FoxTube { seek.Value = videoSource.Position.TotalSeconds; seekIndicator.Value = seek.Value; + /*if (Math.Round(videoSource.Position.TotalSeconds, 1) != Math.Round(audioSource.Position.TotalSeconds, 1)) + { + Debug.WriteLine($"Correcting tracks synchronization (Video track position: {videoSource.Position}; Audio track position: {audioSource.Position})"); + audioSource.Position = videoSource.Position; + }*/ } void Elapsed() { controls.Visibility = Visibility.Collapsed; - if (!miniView) + if (!MiniView) touchCentral.Visibility = Visibility.Collapsed; if (pointerCaptured) + { + cursorPositionBackup = Window.Current.CoreWindow.PointerPosition; Window.Current.CoreWindow.PointerCursor = null; + } seekIndicator.Visibility = Visibility.Collapsed; t.Stop(); } public void UpdateSize() { - if(miniView) + if(MiniView) { Height = Window.Current.Bounds.Height; Debug.WriteLine("Video player aspect ratio has been corrected."); @@ -258,19 +277,19 @@ namespace FoxTube { double v = volume.Value; if (v == 0) - muteBtn.Content = openVolume.Content = ""; + muteBtn.Content = openVolume.Content = "\xE74F"; else if (v <= 25 && v > 0) - muteBtn.Content = openVolume.Content = ""; + muteBtn.Content = openVolume.Content = "\xE992"; else if (v <= 50 && v > 25) - muteBtn.Content = openVolume.Content = ""; + muteBtn.Content = openVolume.Content = "\xE993"; else if (v <= 75 && v > 50) - muteBtn.Content = openVolume.Content = ""; + muteBtn.Content = openVolume.Content = "\xE994"; else if (v > 75) - muteBtn.Content = openVolume.Content = ""; + muteBtn.Content = openVolume.Content = "\xE995"; settings.Values["volume"] = volume.Value; - videoSource.Volume = volume.Value * 0.01; + audioSource.Volume = videoSource.Volume = volume.Value * 0.01; } private void muteBtn_Click(object sender, RoutedEventArgs e) @@ -298,13 +317,17 @@ namespace FoxTube private void UserControl_PointerMoved(object sender, PointerRoutedEventArgs e) { + if (cursorPositionBackup == null) + cursorPositionBackup = Window.Current.CoreWindow.PointerPosition; + else if (cursorPositionBackup == Window.Current.CoreWindow.PointerPosition) + return; ShowControls(); } void ShowControls() { controls.Visibility = Visibility.Visible; - if (miniView) + if (MiniView) seekIndicator.Visibility = Visibility.Visible; if (pointerCaptured) Window.Current.CoreWindow.PointerCursor = cursorBackup; @@ -317,32 +340,117 @@ namespace FoxTube try { videoSource.Pause(); + audioSource.Source = null; timecodeBackup = videoSource.Position.TotalSeconds; - switch((quality.SelectedItem as ComboBoxItem).Content) + audioReady = false; + videoReady = false; + + settings.Values["rememberedQuality"] = (quality.SelectedItem as ComboBoxItem).Content as string; + + if(streamInfo.Muxed.ToList().Find(x => x.VideoQualityLabel == (quality.SelectedItem as ComboBoxItem).Content as string) != null) + { + isMuxed = true; + videoSource.Source = streamInfo.Muxed.First(x => x.VideoQualityLabel == (quality.SelectedItem as ComboBoxItem).Content as string).Url.ToUri(); + } + else + { + isMuxed = false; + videoSource.Source = streamInfo.Video.First(x => x.VideoQualityLabel == (quality.SelectedItem as ComboBoxItem).Content as string).Url.ToUri(); + audioSource.Source = streamInfo.Audio.First().Url.ToUri(); + } + + /*switch((quality.SelectedItem as ComboBoxItem).Content) { case "2160p": - videoSource.Source = new Uri(streamInfo.Muxed.First(x => x.VideoQuality == VideoQuality.High2160).Url); + if(streamInfo.Muxed.First(x => x.VideoQuality.)) + { + isMuxed = true; + + } + else + { + isMuxed = false; + audioSource.Source = (streamInfo.Find(x => x.AudioQuality == YouTubeQuality.QualityHigh && !x.HasVideo) ?? + streamInfo.Find(x => x.AudioQuality == YouTubeQuality.QualityMedium && !x.HasVideo) ?? + streamInfo.Find(x => x.AudioQuality == YouTubeQuality.QualityLow && !x.HasVideo) ?? + streamInfo.Find(x => x.HasAudio && !x.HasVideo)).Uri; + } + videoSource.Source = streamInfo.First(x => x.VideoQuality == YouTubeQuality.Quality2160P).Uri; break; case "1080p": - videoSource.Source = new Uri(streamInfo.Muxed.First(x => x.VideoQuality == VideoQuality.High1080).Url); + if (streamInfo.First(x => x.VideoQuality == YouTubeQuality.Quality1080P).HasAudio) + isMuxed = true; + else + { + isMuxed = false; + audioSource.Source = (streamInfo.Find(x => x.AudioQuality == YouTubeQuality.QualityHigh && !x.HasVideo) ?? + streamInfo.Find(x => x.AudioQuality == YouTubeQuality.QualityMedium && !x.HasVideo) ?? + streamInfo.Find(x => x.AudioQuality == YouTubeQuality.QualityLow && !x.HasVideo) ?? + streamInfo.Find(x => x.HasAudio && !x.HasVideo)).Uri; + } + videoSource.Source = streamInfo.First(x => x.VideoQuality == YouTubeQuality.Quality1080P).Uri; break; case "720p": - videoSource.Source = new Uri(streamInfo.Muxed.First(x => x.VideoQuality == VideoQuality.High720).Url); + if (streamInfo.First(x => x.VideoQuality == YouTubeQuality.Quality720P).HasAudio) + isMuxed = true; + else + { + isMuxed = false; + audioSource.Source = (streamInfo.Find(x => x.AudioQuality == YouTubeQuality.QualityHigh && !x.HasVideo) ?? + streamInfo.Find(x => x.AudioQuality == YouTubeQuality.QualityMedium && !x.HasVideo) ?? + streamInfo.Find(x => x.AudioQuality == YouTubeQuality.QualityLow && !x.HasVideo) ?? + streamInfo.Find(x => x.HasAudio && !x.HasVideo)).Uri; + } + videoSource.Source = streamInfo.First(x => x.VideoQuality == YouTubeQuality.Quality720P).Uri; break; case "480p": - videoSource.Source = new Uri(streamInfo.Muxed.First(x => x.VideoQuality == VideoQuality.Medium480).Url); + if (streamInfo.First(x => x.VideoQuality == YouTubeQuality.Quality480P).HasAudio) + isMuxed = true; + else + { + isMuxed = false; + audioSource.Source = (streamInfo.Find(x => x.AudioQuality == YouTubeQuality.QualityMedium && !x.HasVideo) ?? + streamInfo.Find(x => x.AudioQuality == YouTubeQuality.QualityLow && !x.HasVideo) ?? + streamInfo.Find(x => x.HasAudio && !x.HasVideo)).Uri; + } + videoSource.Source = streamInfo.First(x => x.VideoQuality == YouTubeQuality.Quality480P).Uri; break; case "360p": - videoSource.Source = new Uri(streamInfo.Muxed.First(x => x.VideoQuality == VideoQuality.Medium360).Url); + if (streamInfo.First(x => x.VideoQuality == YouTubeQuality.Quality360P).HasAudio) + isMuxed = true; + else + { + isMuxed = false; + audioSource.Source = (streamInfo.Find(x => x.AudioQuality == YouTubeQuality.QualityMedium && !x.HasVideo) ?? + streamInfo.Find(x => x.AudioQuality == YouTubeQuality.QualityLow && !x.HasVideo) ?? + streamInfo.Find(x => x.HasAudio && !x.HasVideo)).Uri; + } + videoSource.Source = streamInfo.First(x => x.VideoQuality == YouTubeQuality.Quality360P).Uri; break; case "240p": - videoSource.Source = new Uri(streamInfo.Muxed.First(x => x.VideoQuality == VideoQuality.Low240).Url); + if (streamInfo.First(x => x.VideoQuality == YouTubeQuality.Quality240P).HasAudio) + isMuxed = true; + else + { + isMuxed = false; + audioSource.Source = (streamInfo.Find(x => x.AudioQuality == YouTubeQuality.QualityLow && !x.HasVideo) ?? + streamInfo.Find(x => x.HasAudio && !x.HasVideo)).Uri; + } + videoSource.Source = streamInfo.First(x => x.VideoQuality == YouTubeQuality.Quality240P).Uri; break; case "144p": - videoSource.Source = new Uri(streamInfo.Muxed.First(x => x.VideoQuality == VideoQuality.Low144).Url); + if (streamInfo.First(x => x.VideoQuality == YouTubeQuality.Quality144P).HasAudio) + isMuxed = true; + else + { + isMuxed = false; + audioSource.Source = (streamInfo.Find(x => x.AudioQuality == YouTubeQuality.QualityLow && !x.HasVideo) ?? + streamInfo.Find(x => x.HasAudio && !x.HasVideo)).Uri; + } + videoSource.Source = streamInfo.First(x => x.VideoQuality == YouTubeQuality.Quality144P).Uri; break; - } + }*/ needUpdateTimecode = true; videoSource.Play(); @@ -367,8 +475,10 @@ namespace FoxTube { if (subsSwitch.IsOn) { - - captions.Initialize(ccInfo[subsLang.SelectedIndex].Url); + if (ccInfo[subsLang.SelectedIndex].IsAutoGenerated) + captions.Initialize(ccInfo[subsLang.SelectedIndex].Url.Replace("format=3", "format=0"), true); + else + captions.Initialize(ccInfo[subsLang.SelectedIndex].Url); } else captions.Close(); @@ -399,7 +509,45 @@ namespace FoxTube if (videoSource.CurrentState == MediaElementState.Playing) videoSource.Pause(); else if (videoSource.CurrentState == MediaElementState.Paused) - videoSource.Play(); + if((audioReady && videoReady) || isMuxed) + videoSource.Play(); + } + + private void AudioSource_CurrentStateChanged(object sender, RoutedEventArgs e) + { + switch (audioSource.CurrentState) + { + case MediaElementState.Buffering: + if(videoSource.CurrentState != MediaElementState.Buffering) + videoSource.Pause(); + break; + + case MediaElementState.Playing: + if (videoSource.CurrentState == MediaElementState.Paused) + videoSource.Play(); + else if (videoSource.CurrentState == MediaElementState.Buffering) + audioSource.Pause(); + break; + } + } + + private void videoSource_Opened(object sender, RoutedEventArgs arg) + { + if(!isMuxed) + { + if(sender == videoSource) + { + videoReady = true; + if (audioReady && ((timecodeBackup == 0 && (bool)settings.Values["videoAutoplay"]) || timecodeBackup > 0)) + play_Click(this, null); + } + else if(sender == audioSource) + { + audioReady = true; + if (videoReady && ((timecodeBackup == 0 && (bool)settings.Values["videoAutoplay"]) || timecodeBackup > 0)) + play_Click(this, null); + } + } } private void videoSource_CurrentStateChanged(object sender, RoutedEventArgs e) @@ -413,6 +561,8 @@ namespace FoxTube switch(videoSource.CurrentState) { case MediaElementState.Buffering: + audioSource.Position = videoSource.Position; + audioSource.Pause(); bufferingBar.Visibility = Visibility.Visible; seek.IsEnabled = false; @@ -426,6 +576,8 @@ namespace FoxTube break; case MediaElementState.Paused: + if(audioSource.CurrentState != MediaElementState.Buffering) + audioSource.Pause(); bufferingBar.Visibility = Visibility.Collapsed; seek.IsEnabled = true; @@ -439,12 +591,15 @@ namespace FoxTube break; case MediaElementState.Playing: + audioSource.Play(); bufferingBar.Visibility = Visibility.Collapsed; seek.IsEnabled = true; play.IsEnabled = true; touchPlay.IsEnabled = true; + seekTimer.Start(); + play.Content = "\xE103"; touchPlay.Content = "\xE103"; @@ -452,6 +607,7 @@ namespace FoxTube break; default: + audioSource.Pause(); bufferingBar.Visibility = Visibility.Collapsed; systemControls.PlaybackStatus = MediaPlaybackStatus.Closed; break; @@ -461,10 +617,10 @@ namespace FoxTube private async void miniView_Click(object sender, RoutedEventArgs e) { ApplicationViewTitleBar titleBar = ApplicationView.GetForCurrentView().TitleBar; - miniView = !miniView; - SetFullSize(this, miniView); + MiniView = !MiniView; + SetFullSize(this, MiniView); - if (miniView) + if (MiniView) { if (fullScreen) { @@ -590,7 +746,7 @@ namespace FoxTube Width = 432; Height = 243; - miniView = true; + MiniView = true; Methods.MainPage.MinimizeVideo(); mainControls.Visibility = Visibility.Collapsed; @@ -607,6 +763,8 @@ namespace FoxTube private void close_Click(object sender, RoutedEventArgs e) { systemControls.IsEnabled = false; + pointerCaptured = false; + Elapsed(); Methods.MainPage.CloseVideo(); } @@ -617,7 +775,7 @@ namespace FoxTube Width = double.NaN; Height = double.NaN; - miniView = false; + MiniView = false; Methods.MainPage.MaximizeVideo(); mainControls.Visibility = Visibility.Visible; @@ -655,38 +813,38 @@ namespace FoxTube private void playPauseArea_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e) { - if (miniView && ApplicationView.GetForCurrentView().ViewMode == ApplicationViewMode.CompactOverlay) + if (MiniView && ApplicationView.GetForCurrentView().ViewMode == ApplicationViewMode.CompactOverlay) miniView_Click(this, null); - else if (miniView && ApplicationView.GetForCurrentView().ViewMode == ApplicationViewMode.Default) + else if (MiniView && ApplicationView.GetForCurrentView().ViewMode == ApplicationViewMode.Default) maximize_Click(this, null); else if (fullScreen) fullscreen_Click(this, null); - else if (!miniView && !fullScreen) + else if (!MiniView && !fullScreen) fullscreen_Click(this, null); } private void playPauseArea_Tapped(object sender, TappedRoutedEventArgs e) { - if (e.PointerDeviceType == Windows.Devices.Input.PointerDeviceType.Mouse && !miniView) + if (e.PointerDeviceType == Windows.Devices.Input.PointerDeviceType.Mouse && !MiniView) play_Click(this, null); } - private void UserControl_KeyUp(object sender, KeyRoutedEventArgs e) + public void KeyUpPressed(object sender, KeyRoutedEventArgs e) { switch(e.Key) { - case Windows.System.VirtualKey.Escape: - case Windows.System.VirtualKey.F11: + case VirtualKey.Escape: + case VirtualKey.F11: if (fullScreen) fullscreen_Click(this, null); break; - case Windows.System.VirtualKey.Space: + case VirtualKey.Space: play_Click(this, null); break; - case Windows.System.VirtualKey.Left: + case VirtualKey.Left: back10_Click(this, null); break; - case Windows.System.VirtualKey.Right: + case VirtualKey.Right: fwd30_Click(this, null); break; } @@ -694,8 +852,12 @@ namespace FoxTube private void subsLang_SelectionChanged(object sender, SelectionChangedEventArgs e) { - if (subsSwitch.IsOn) - captions.Initialize(ccInfo[subsLang.SelectedIndex].Url); + LoadTrack(); + } + + private void videoSource_BufferingProgressChanged(object sender, RoutedEventArgs e) + { + bufferingLevel.Value = (audioSource.Source != null && videoSource.BufferingProgress > audioSource.BufferingProgress ? audioSource.BufferingProgress : videoSource.BufferingProgress) * 100; } } } diff --git a/FoxTube/FoxTube.csproj b/FoxTube/FoxTube.csproj index 3110a13..52e3fc3 100644 --- a/FoxTube/FoxTube.csproj +++ b/FoxTube/FoxTube.csproj @@ -97,6 +97,7 @@ App.xaml + @@ -398,7 +399,7 @@ - 1.0.1 + 1.1.0 1.30.0-beta02 @@ -419,25 +420,25 @@ 10.1805.7001 - 6.1.5 + 6.1.9 - 3.0.0 + 4.0.0 - 3.0.0 - - - 2.5.16 - - - 2.5.16 + 4.0.0 - 4.3.1 + 4.3.2 + + + 1.2.0 - 4.3.1 + 4.4.0 + + + 0.10.11 diff --git a/FoxTube/Pages/ChannelPage.xaml.cs b/FoxTube/Pages/ChannelPage.xaml.cs index a419167..6b7453c 100644 --- a/FoxTube/Pages/ChannelPage.xaml.cs +++ b/FoxTube/Pages/ChannelPage.xaml.cs @@ -86,6 +86,9 @@ namespace FoxTube.Pages try { channelId = id; + if (Methods.NeedToResponse) + Methods.MainPage.content_Navigated(this, null); + ChannelsResource.ListRequest request = SecretsVault.Service.Channels.List("snippet,statistics,brandingSettings"); request.Id = id; if (content.Items.Count == 4) @@ -133,8 +136,6 @@ namespace FoxTube.Pages if (SecretsVault.IsAuthorized) { - SecretsVault.Subscriptions.ForEach(x => Debug.WriteLine($"{x.Snippet.Title}: {x.Snippet.ResourceId.ChannelId}")); - Debug.WriteLine($"Current channel ID: {item.Id}"); bool b = false; foreach (Subscription s in SecretsVault.Subscriptions) { @@ -148,7 +149,6 @@ namespace FoxTube.Pages break; } } - Debug.WriteLine($"Channel was found: {b}"); subscriptionPane.Visibility = Visibility.Visible; } @@ -250,23 +250,17 @@ namespace FoxTube.Pages private async void subscribe_Click(object sender, RoutedEventArgs e) { - if (subscribe.Background == new SolidColorBrush(Colors.Red)) + if(await SecretsVault.ChangeSubscriptionState(channelId)) { - if (await SecretsVault.Subscribe(channelId)) - { - subscribe.Background = new SolidColorBrush(Colors.Transparent); - subscribe.Foreground = new SolidColorBrush(Colors.Gray); - subscribe.Content = "Subscribed"; - } + subscribe.Background = new SolidColorBrush(Colors.Transparent); + subscribe.Foreground = new SolidColorBrush(Colors.Gray); + subscribe.Content = "Subscribed"; } else { - if (await SecretsVault.Unsubscibe(channelId)) - { - subscribe.Background = new SolidColorBrush(Colors.Red); - subscribe.Foreground = new SolidColorBrush(Colors.White); - subscribe.Content = "Subscribe"; - } + subscribe.Background = new SolidColorBrush(Colors.Red); + subscribe.Foreground = new SolidColorBrush(Colors.White); + subscribe.Content = "Subscribe"; } } diff --git a/FoxTube/Pages/CommentsPage.xaml b/FoxTube/Pages/CommentsPage.xaml index dbaa61b..cff7030 100644 --- a/FoxTube/Pages/CommentsPage.xaml +++ b/FoxTube/Pages/CommentsPage.xaml @@ -18,7 +18,7 @@ - + - - - - - - + + + + + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - + + + - - - - - + + + + + + - - - + diff --git a/FoxTube/Pages/VideoPage.xaml.cs b/FoxTube/Pages/VideoPage.xaml.cs index 80971e9..8a632f2 100644 --- a/FoxTube/Pages/VideoPage.xaml.cs +++ b/FoxTube/Pages/VideoPage.xaml.cs @@ -1,36 +1,21 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Runtime.InteropServices.WindowsRuntime; using Windows.Foundation; -using Windows.Foundation.Collections; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Controls.Primitives; -using Windows.UI.Xaml.Data; -using Windows.UI.Xaml.Input; using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Navigation; using Windows.System; - using Google.Apis.YouTube.v3.Data; using Google.Apis.YouTube.v3; using Windows.UI.Xaml.Media.Imaging; using System.Diagnostics; -using System.Timers; -using Windows.UI.Core; -using System.Threading; using Windows.ApplicationModel.DataTransfer; using Windows.Storage; using Windows.ApplicationModel; using Windows.Storage.Streams; -using Windows.UI.Popups; -using Windows.UI.Text; using Windows.UI; -using FoxTube.Pages; - -using MyToolkit.Multimedia; using FoxTube.Controls; // The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238 @@ -63,9 +48,7 @@ namespace FoxTube.Pages public string videoId; string playlistId = null; - Video item; - - List downloads = new List(); + public Video item; bool wide; bool isExtended = false; @@ -92,11 +75,7 @@ namespace FoxTube.Pages { Debug.WriteLine("Correcting layout..."); mainContent.Children.Remove(descriptionPanel); - pivot.Items.Insert(0, new PivotItem() - { - Content = descriptionPanel, - Header = "Description" - }); + pivot.Items.Insert(0, descriptionPanel); tabsPlaceholder.Children.Remove(pivot); mainContent.Children.Add(pivot); @@ -256,40 +235,38 @@ namespace FoxTube.Pages async void LoadDownloads() { - downloadSelector.Items.Clear(); - - YouTubeUri[] uris = await YouTube.GetUrisAsync(item.Id); - if (uris.Length > 0) + /*List uris = (await YouTube.GetUrisAsync(item.Id)).ToList(); + if (uris.Count > 0) foreach (YouTubeUri u in uris) { if (u.HasAudio && u.HasVideo) { - downloads.Add(u.Uri.AbsoluteUri); MenuFlyoutItem menuItem = new MenuFlyoutItem() { - Text = Methods.QualityToString(u.VideoQuality) + Text = Methods.QualityToString(u.VideoQuality), + Tag = u.Uri.AbsoluteUri }; menuItem.Click += downloadItemSelected; downloadSelector.Items.Add(menuItem); } else if (u.HasAudio) { - downloads.Add(u.Uri.AbsoluteUri); MenuFlyoutItem menuItem = new MenuFlyoutItem() { - Text = Methods.QualityToString(u.AudioQuality) + Text = Methods.QualityToString(u.AudioQuality), + Tag = u.Uri.AbsoluteUri }; menuItem.Click += downloadItemSelected; downloadSelector.Items.Add(menuItem); } } else - download.Visibility = Visibility.Collapsed; + download.Visibility = Visibility.Collapsed;*/ } private void downloadItemSelected(object sender, RoutedEventArgs e) { - throw new NotImplementedException(); + Methods.MainPage.Agent.Add((sender as MenuFlyoutItem).Tag.ToString()); } async void LoadRelatedVideos() @@ -360,39 +337,30 @@ namespace FoxTube.Pages else wide = false; - if (e.NewSize.Width > 1000 && e.PreviousSize.Width <= 1000 && !isExtended) + if (e.NewSize.Width > 1000 && mainContent.Children.Contains(pivot) && !isExtended) { - if (mainContent.Children.Contains(pivot)) - { - mainContent.Children.Remove(pivot); - tabsPlaceholder.Children.Add(pivot); + mainContent.Children.Remove(pivot); + tabsPlaceholder.Children.Add(pivot); + + pivot.Items.RemoveAt(0); + mainContent.Children.Add(descriptionPanel); - (pivot.Items[0] as PivotItem).Content = null; - pivot.Items.RemoveAt(0); - mainContent.Children.Add(descriptionPanel); - } - grid.ColumnDefinitions[1].Width = new GridLength(400); + + pivot.SelectedIndex = 0; } - else if (e.NewSize.Width <= 1000 & e.PreviousSize.Width > 1000) + else if (e.NewSize.Width <= 1000 && mainContent.Children.Contains(descriptionPanel)) { - if (mainContent.Children.Contains(descriptionPanel)) - { - mainContent.Children.Remove(descriptionPanel); - pivot.Items.Insert(0, new PivotItem() - { - Content = descriptionPanel, - Header = "Description" - }); + mainContent.Children.Remove(descriptionPanel); + pivot.Items.Insert(0, descriptionPanel); + + tabsPlaceholder.Children.Remove(pivot); + mainContent.Children.Add(pivot); - tabsPlaceholder.Children.Remove(pivot); - mainContent.Children.Add(pivot); - } - grid.ColumnDefinitions[1].Width = new GridLength(0); - } - pivot.SelectedIndex = 0; + pivot.SelectedIndex = 0; + } } private async void Share(DataTransferManager sender, DataRequestedEventArgs args) @@ -450,7 +418,7 @@ namespace FoxTube.Pages dislike.Foreground = new SolidColorBrush(Colors.Red); dislikes.Text = (int.Parse(dislikes.Text, System.Globalization.NumberStyles.AllowThousands) + 1).ToString("0,0"); - rating.Value = (int)((item.Statistics.LikeCount - 1) / (item.Statistics.DislikeCount + item.Statistics.LikeCount) * 100); + rating.Value--; await SecretsVault.Service.Videos.Rate(videoId, VideosResource.RateRequest.RatingEnum.Dislike).ExecuteAsync(); userRating = Rating.Dislike; @@ -459,7 +427,7 @@ namespace FoxTube.Pages case Rating.None: dislike.Foreground = new SolidColorBrush(Colors.Red); dislikes.Text = (int.Parse(dislikes.Text, System.Globalization.NumberStyles.AllowThousands) + 1).ToString("0,0"); - rating.Value = (int)((item.Statistics.LikeCount) / (item.Statistics.DislikeCount + item.Statistics.LikeCount + 1) * 100); + rating.Maximum++; await SecretsVault.Service.Videos.Rate(videoId, VideosResource.RateRequest.RatingEnum.Dislike).ExecuteAsync(); userRating = Rating.Dislike; @@ -468,7 +436,7 @@ namespace FoxTube.Pages case Rating.Dislike: dislike.Foreground = new SolidColorBrush(Colors.Gray); dislikes.Text = (int.Parse(dislikes.Text, System.Globalization.NumberStyles.AllowThousands) - 1).ToString("0,0"); - rating.Value = (int)((item.Statistics.LikeCount) / (item.Statistics.DislikeCount + item.Statistics.LikeCount - 1) * 100); + rating.Maximum--; await SecretsVault.Service.Videos.Rate(videoId, VideosResource.RateRequest.RatingEnum.None).ExecuteAsync(); break; } @@ -485,7 +453,7 @@ namespace FoxTube.Pages like.Foreground = new SolidColorBrush(Colors.Green); likes.Text = (int.Parse(likes.Text, System.Globalization.NumberStyles.AllowThousands) + 1).ToString("0,0"); - rating.Value = (int)((item.Statistics.LikeCount + 1) / (item.Statistics.DislikeCount + item.Statistics.LikeCount) * 100); + rating.Value++; await SecretsVault.Service.Videos.Rate(videoId, VideosResource.RateRequest.RatingEnum.Like).ExecuteAsync(); userRating = Rating.Like; @@ -494,7 +462,8 @@ namespace FoxTube.Pages case Rating.None: like.Foreground = new SolidColorBrush(Colors.Green); likes.Text = (int.Parse(likes.Text, System.Globalization.NumberStyles.AllowThousands) + 1).ToString("0,0"); - rating.Value = (int)((item.Statistics.LikeCount + 1) / (item.Statistics.DislikeCount + item.Statistics.LikeCount + 1) * 100); + rating.Maximum++; + rating.Value++; await SecretsVault.Service.Videos.Rate(videoId, VideosResource.RateRequest.RatingEnum.Like).ExecuteAsync(); userRating = Rating.Like; @@ -503,7 +472,8 @@ namespace FoxTube.Pages case Rating.Like: like.Foreground = new SolidColorBrush(Colors.Gray); likes.Text = (int.Parse(likes.Text, System.Globalization.NumberStyles.AllowThousands) - 1).ToString("0,0"); - rating.Value = (int)((item.Statistics.LikeCount - 1) / (item.Statistics.DislikeCount + item.Statistics.LikeCount - 1) * 100); + rating.Maximum--; + rating.Value--; await SecretsVault.Service.Videos.Rate(videoId, VideosResource.RateRequest.RatingEnum.None).ExecuteAsync(); break; } @@ -521,23 +491,17 @@ namespace FoxTube.Pages private async void subscribe_Click(object sender, RoutedEventArgs e) { - if (subscribe.Background == new SolidColorBrush(Colors.Red)) + if (await SecretsVault.ChangeSubscriptionState(item.Snippet.ChannelId)) { - if (await SecretsVault.Subscribe(item.Snippet.ChannelId)) - { - subscribe.Background = new SolidColorBrush(Colors.Transparent); - subscribe.Foreground = new SolidColorBrush(Colors.Gray); - subscribe.Content = "Subscribed"; - } + subscribe.Background = new SolidColorBrush(Colors.Transparent); + subscribe.Foreground = new SolidColorBrush(Colors.Gray); + subscribe.Content = "Subscribed"; } else { - if (await SecretsVault.Unsubscibe(item.Snippet.ChannelId)) - { - subscribe.Background = new SolidColorBrush(Colors.Red); - subscribe.Foreground = new SolidColorBrush(Colors.White); - subscribe.Content = "Subscribe"; - } + subscribe.Background = new SolidColorBrush(Colors.Red); + subscribe.Foreground = new SolidColorBrush(Colors.White); + subscribe.Content = "Subscribe"; } } }