diff --git a/FoxTube.Background/BackgroundProcessor.cs b/FoxTube.Background/BackgroundProcessor.cs index 6e06785..3819038 100644 --- a/FoxTube.Background/BackgroundProcessor.cs +++ b/FoxTube.Background/BackgroundProcessor.cs @@ -123,7 +123,7 @@ namespace FoxTube.Background { public static string ConvertEscapeSymbols(this string str) { - return str.Replace(""", "\"").Replace("'", "'").Replace("<", "<").Replace(">", ">").Replace("&", "&"); + return str.Replace(""", "\"").Replace("'", "'").Replace("<", "<").Replace(">", ">").Replace("&", "&").Replace(""", "\"").Replace("'", "'").Replace("<", "<").Replace(">", ">").Replace("&", "&"); } } } diff --git a/FoxTube.Tests/Assets/LockScreenLogo.scale-200.png b/FoxTube.Tests/Assets/LockScreenLogo.scale-200.png new file mode 100644 index 0000000..735f57a Binary files /dev/null and b/FoxTube.Tests/Assets/LockScreenLogo.scale-200.png differ diff --git a/FoxTube.Tests/Assets/SplashScreen.scale-200.png b/FoxTube.Tests/Assets/SplashScreen.scale-200.png new file mode 100644 index 0000000..023e7f1 Binary files /dev/null and b/FoxTube.Tests/Assets/SplashScreen.scale-200.png differ diff --git a/FoxTube.Tests/Assets/Square150x150Logo.scale-200.png b/FoxTube.Tests/Assets/Square150x150Logo.scale-200.png new file mode 100644 index 0000000..af49fec Binary files /dev/null and b/FoxTube.Tests/Assets/Square150x150Logo.scale-200.png differ diff --git a/FoxTube.Tests/Assets/Square44x44Logo.scale-200.png b/FoxTube.Tests/Assets/Square44x44Logo.scale-200.png new file mode 100644 index 0000000..ce342a2 Binary files /dev/null and b/FoxTube.Tests/Assets/Square44x44Logo.scale-200.png differ diff --git a/FoxTube.Tests/Assets/Square44x44Logo.targetsize-24_altform-unplated.png b/FoxTube.Tests/Assets/Square44x44Logo.targetsize-24_altform-unplated.png new file mode 100644 index 0000000..f6c02ce Binary files /dev/null and b/FoxTube.Tests/Assets/Square44x44Logo.targetsize-24_altform-unplated.png differ diff --git a/FoxTube.Tests/Assets/StoreLogo.png b/FoxTube.Tests/Assets/StoreLogo.png new file mode 100644 index 0000000..7385b56 Binary files /dev/null and b/FoxTube.Tests/Assets/StoreLogo.png differ diff --git a/FoxTube.Tests/Assets/Wide310x150Logo.scale-200.png b/FoxTube.Tests/Assets/Wide310x150Logo.scale-200.png new file mode 100644 index 0000000..288995b Binary files /dev/null and b/FoxTube.Tests/Assets/Wide310x150Logo.scale-200.png differ diff --git a/FoxTube.Tests/FoxTube.Tests.csproj b/FoxTube.Tests/FoxTube.Tests.csproj new file mode 100644 index 0000000..9136def --- /dev/null +++ b/FoxTube.Tests/FoxTube.Tests.csproj @@ -0,0 +1,172 @@ + + + + + Debug + x86 + {3D864717-2D87-4E54-BFC0-755FC2FCA2A7} + AppContainerExe + Properties + FoxTube.Tests + FoxTube.Tests + en-US + UAP + 10.0.17134.0 + 10.0.17134.0 + 14 + 512 + {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + FoxTube.Tests_TemporaryKey.pfx + $(VisualStudioVersion) + + + true + bin\x86\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + x86 + false + prompt + true + + + bin\x86\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + x86 + false + prompt + true + true + + + true + bin\ARM\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + ARM + false + prompt + true + + + bin\ARM\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + ARM + false + prompt + true + true + + + true + bin\ARM64\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + ARM64 + false + prompt + true + true + + + bin\ARM64\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + ARM64 + false + prompt + true + true + + + true + bin\x64\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + x64 + false + prompt + true + + + bin\x64\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + x64 + false + prompt + true + true + + + PackageReference + + + + + + + + UnitTestApp.xaml + + + + + + MSBuild:Compile + Designer + + + + + Designer + + + + + + + + + + + + + + + + 6.2.8 + + + 1.4.0 + + + 1.4.0 + + + + 14.0 + + + + \ No newline at end of file diff --git a/FoxTube.Tests/Package.appxmanifest b/FoxTube.Tests/Package.appxmanifest new file mode 100644 index 0000000..c1d5753 --- /dev/null +++ b/FoxTube.Tests/Package.appxmanifest @@ -0,0 +1,46 @@ + + + + + + + + + FoxTube.Tests + XFox + Assets\StoreLogo.png + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/FoxTube.Tests/Properties/AssemblyInfo.cs b/FoxTube.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..c55a525 --- /dev/null +++ b/FoxTube.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,18 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("FoxTube.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("FoxTube.Tests")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: AssemblyMetadata("TargetPlatform","UAP")] + +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: ComVisible(false)] \ No newline at end of file diff --git a/FoxTube.Tests/Properties/UnitTestApp.rd.xml b/FoxTube.Tests/Properties/UnitTestApp.rd.xml new file mode 100644 index 0000000..996a839 --- /dev/null +++ b/FoxTube.Tests/Properties/UnitTestApp.rd.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/FoxTube.Tests/UnitTest.cs b/FoxTube.Tests/UnitTest.cs new file mode 100644 index 0000000..16e64c1 --- /dev/null +++ b/FoxTube.Tests/UnitTest.cs @@ -0,0 +1,15 @@ + +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace FoxTube.Tests +{ + [TestClass] + public class UnitTest1 + { + [TestMethod] + public void TestMethod1() + { + } + } +} diff --git a/FoxTube.Tests/UnitTestApp.xaml b/FoxTube.Tests/UnitTestApp.xaml new file mode 100644 index 0000000..32e7cb5 --- /dev/null +++ b/FoxTube.Tests/UnitTestApp.xaml @@ -0,0 +1,7 @@ + + + diff --git a/FoxTube.Tests/UnitTestApp.xaml.cs b/FoxTube.Tests/UnitTestApp.xaml.cs new file mode 100644 index 0000000..df5c576 --- /dev/null +++ b/FoxTube.Tests/UnitTestApp.xaml.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; +using Windows.ApplicationModel; +using Windows.ApplicationModel.Activation; +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; + +namespace FoxTube.Tests +{ + /// + /// Provides application-specific behavior to supplement the default Application class. + /// + sealed partial class App : Application + { + /// + /// Initializes the singleton application object. This is the first line of authored code + /// executed, and as such is the logical equivalent of main() or WinMain(). + /// + public App() + { + this.InitializeComponent(); + this.Suspending += OnSuspending; + } + + /// + /// Invoked when the application is launched normally by the end user. Other entry points + /// will be used such as when the application is launched to open a specific file. + /// + /// Details about the launch request and process. + protected override void OnLaunched(LaunchActivatedEventArgs e) + { + +#if DEBUG + if (System.Diagnostics.Debugger.IsAttached) + { + this.DebugSettings.EnableFrameRateCounter = true; + } +#endif + + Frame rootFrame = Window.Current.Content as Frame; + + // Do not repeat app initialization when the Window already has content, + // just ensure that the window is active + if (rootFrame == null) + { + // Create a Frame to act as the navigation context and navigate to the first page + rootFrame = new Frame(); + + rootFrame.NavigationFailed += OnNavigationFailed; + + if (e.PreviousExecutionState == ApplicationExecutionState.Terminated) + { + //TODO: Load state from previously suspended application + } + + // Place the frame in the current Window + Window.Current.Content = rootFrame; + } + + Microsoft.VisualStudio.TestPlatform.TestExecutor.UnitTestClient.CreateDefaultUI(); + + // Ensure the current window is active + Window.Current.Activate(); + + Microsoft.VisualStudio.TestPlatform.TestExecutor.UnitTestClient.Run(e.Arguments); + } + + /// + /// Invoked when Navigation to a certain page fails + /// + /// The Frame which failed navigation + /// Details about the navigation failure + void OnNavigationFailed(object sender, NavigationFailedEventArgs e) + { + throw new Exception("Failed to load Page " + e.SourcePageType.FullName); + } + + /// + /// Invoked when application execution is being suspended. Application state is saved + /// without knowing whether the application will be terminated or resumed with the contents + /// of memory still intact. + /// + /// The source of the suspend request. + /// Details about the suspend request. + private void OnSuspending(object sender, SuspendingEventArgs e) + { + var deferral = e.SuspendingOperation.GetDeferral(); + //TODO: Save application state and stop any background activity + deferral.Complete(); + } + } +} diff --git a/FoxTube.sln b/FoxTube.sln index 7357c1c..e8ba8c1 100644 --- a/FoxTube.sln +++ b/FoxTube.sln @@ -1,20 +1,24 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27428.1 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28803.352 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FoxTube", "FoxTube\FoxTube.csproj", "{2597B816-7316-4D20-BA6C-D78001E89C1A}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FoxTube.Background", "FoxTube.Background\FoxTube.Background.csproj", "{FC9128D7-E3AA-48ED-8641-629794B88B28}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FoxTube.Tests", "FoxTube.Tests\FoxTube.Tests.csproj", "{3D864717-2D87-4E54-BFC0-755FC2FCA2A7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Debug|ARM = Debug|ARM + Debug|ARM64 = Debug|ARM64 Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU Release|ARM = Release|ARM + Release|ARM64 = Release|ARM64 Release|x64 = Release|x64 Release|x86 = Release|x86 EndGlobalSection @@ -23,6 +27,7 @@ Global {2597B816-7316-4D20-BA6C-D78001E89C1A}.Debug|ARM.ActiveCfg = Debug|ARM {2597B816-7316-4D20-BA6C-D78001E89C1A}.Debug|ARM.Build.0 = Debug|ARM {2597B816-7316-4D20-BA6C-D78001E89C1A}.Debug|ARM.Deploy.0 = Debug|ARM + {2597B816-7316-4D20-BA6C-D78001E89C1A}.Debug|ARM64.ActiveCfg = Debug|x86 {2597B816-7316-4D20-BA6C-D78001E89C1A}.Debug|x64.ActiveCfg = Debug|x64 {2597B816-7316-4D20-BA6C-D78001E89C1A}.Debug|x64.Build.0 = Debug|x64 {2597B816-7316-4D20-BA6C-D78001E89C1A}.Debug|x64.Deploy.0 = Debug|x64 @@ -33,6 +38,7 @@ Global {2597B816-7316-4D20-BA6C-D78001E89C1A}.Release|ARM.ActiveCfg = Release|ARM {2597B816-7316-4D20-BA6C-D78001E89C1A}.Release|ARM.Build.0 = Release|ARM {2597B816-7316-4D20-BA6C-D78001E89C1A}.Release|ARM.Deploy.0 = Release|ARM + {2597B816-7316-4D20-BA6C-D78001E89C1A}.Release|ARM64.ActiveCfg = Release|x86 {2597B816-7316-4D20-BA6C-D78001E89C1A}.Release|x64.ActiveCfg = Release|x64 {2597B816-7316-4D20-BA6C-D78001E89C1A}.Release|x64.Build.0 = Release|x64 {2597B816-7316-4D20-BA6C-D78001E89C1A}.Release|x64.Deploy.0 = Release|x64 @@ -43,6 +49,8 @@ Global {FC9128D7-E3AA-48ED-8641-629794B88B28}.Debug|Any CPU.Build.0 = Debug|Any CPU {FC9128D7-E3AA-48ED-8641-629794B88B28}.Debug|ARM.ActiveCfg = Debug|ARM {FC9128D7-E3AA-48ED-8641-629794B88B28}.Debug|ARM.Build.0 = Debug|ARM + {FC9128D7-E3AA-48ED-8641-629794B88B28}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {FC9128D7-E3AA-48ED-8641-629794B88B28}.Debug|ARM64.Build.0 = Debug|Any CPU {FC9128D7-E3AA-48ED-8641-629794B88B28}.Debug|x64.ActiveCfg = Debug|x64 {FC9128D7-E3AA-48ED-8641-629794B88B28}.Debug|x64.Build.0 = Debug|x64 {FC9128D7-E3AA-48ED-8641-629794B88B28}.Debug|x86.ActiveCfg = Debug|x86 @@ -51,10 +59,38 @@ Global {FC9128D7-E3AA-48ED-8641-629794B88B28}.Release|Any CPU.Build.0 = Release|Any CPU {FC9128D7-E3AA-48ED-8641-629794B88B28}.Release|ARM.ActiveCfg = Release|ARM {FC9128D7-E3AA-48ED-8641-629794B88B28}.Release|ARM.Build.0 = Release|ARM + {FC9128D7-E3AA-48ED-8641-629794B88B28}.Release|ARM64.ActiveCfg = Release|Any CPU + {FC9128D7-E3AA-48ED-8641-629794B88B28}.Release|ARM64.Build.0 = Release|Any CPU {FC9128D7-E3AA-48ED-8641-629794B88B28}.Release|x64.ActiveCfg = Release|x64 {FC9128D7-E3AA-48ED-8641-629794B88B28}.Release|x64.Build.0 = Release|x64 {FC9128D7-E3AA-48ED-8641-629794B88B28}.Release|x86.ActiveCfg = Release|x86 {FC9128D7-E3AA-48ED-8641-629794B88B28}.Release|x86.Build.0 = Release|x86 + {3D864717-2D87-4E54-BFC0-755FC2FCA2A7}.Debug|Any CPU.ActiveCfg = Debug|x86 + {3D864717-2D87-4E54-BFC0-755FC2FCA2A7}.Debug|ARM.ActiveCfg = Debug|ARM + {3D864717-2D87-4E54-BFC0-755FC2FCA2A7}.Debug|ARM.Build.0 = Debug|ARM + {3D864717-2D87-4E54-BFC0-755FC2FCA2A7}.Debug|ARM.Deploy.0 = Debug|ARM + {3D864717-2D87-4E54-BFC0-755FC2FCA2A7}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {3D864717-2D87-4E54-BFC0-755FC2FCA2A7}.Debug|ARM64.Build.0 = Debug|ARM64 + {3D864717-2D87-4E54-BFC0-755FC2FCA2A7}.Debug|ARM64.Deploy.0 = Debug|ARM64 + {3D864717-2D87-4E54-BFC0-755FC2FCA2A7}.Debug|x64.ActiveCfg = Debug|x64 + {3D864717-2D87-4E54-BFC0-755FC2FCA2A7}.Debug|x64.Build.0 = Debug|x64 + {3D864717-2D87-4E54-BFC0-755FC2FCA2A7}.Debug|x64.Deploy.0 = Debug|x64 + {3D864717-2D87-4E54-BFC0-755FC2FCA2A7}.Debug|x86.ActiveCfg = Debug|x86 + {3D864717-2D87-4E54-BFC0-755FC2FCA2A7}.Debug|x86.Build.0 = Debug|x86 + {3D864717-2D87-4E54-BFC0-755FC2FCA2A7}.Debug|x86.Deploy.0 = Debug|x86 + {3D864717-2D87-4E54-BFC0-755FC2FCA2A7}.Release|Any CPU.ActiveCfg = Release|x86 + {3D864717-2D87-4E54-BFC0-755FC2FCA2A7}.Release|ARM.ActiveCfg = Release|ARM + {3D864717-2D87-4E54-BFC0-755FC2FCA2A7}.Release|ARM.Build.0 = Release|ARM + {3D864717-2D87-4E54-BFC0-755FC2FCA2A7}.Release|ARM.Deploy.0 = Release|ARM + {3D864717-2D87-4E54-BFC0-755FC2FCA2A7}.Release|ARM64.ActiveCfg = Release|ARM64 + {3D864717-2D87-4E54-BFC0-755FC2FCA2A7}.Release|ARM64.Build.0 = Release|ARM64 + {3D864717-2D87-4E54-BFC0-755FC2FCA2A7}.Release|ARM64.Deploy.0 = Release|ARM64 + {3D864717-2D87-4E54-BFC0-755FC2FCA2A7}.Release|x64.ActiveCfg = Release|x64 + {3D864717-2D87-4E54-BFC0-755FC2FCA2A7}.Release|x64.Build.0 = Release|x64 + {3D864717-2D87-4E54-BFC0-755FC2FCA2A7}.Release|x64.Deploy.0 = Release|x64 + {3D864717-2D87-4E54-BFC0-755FC2FCA2A7}.Release|x86.ActiveCfg = Release|x86 + {3D864717-2D87-4E54-BFC0-755FC2FCA2A7}.Release|x86.Build.0 = Release|x86 + {3D864717-2D87-4E54-BFC0-755FC2FCA2A7}.Release|x86.Deploy.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/FoxTube/App.xaml.cs b/FoxTube/App.xaml.cs index 771f189..4acf069 100644 --- a/FoxTube/App.xaml.cs +++ b/FoxTube/App.xaml.cs @@ -267,6 +267,7 @@ namespace FoxTube SettingsStorage.SaveData(); DownloadAgent.QuitPrompt(); + Controls.Player.ManifestGenerator.ClearRoaming(); deferral.Complete(); Analytics.TrackEvent("Session terminated"); } diff --git a/FoxTube/Assets/Data/Patchnotes.xml b/FoxTube/Assets/Data/Patchnotes.xml index 5bb3172..3052bc7 100644 --- a/FoxTube/Assets/Data/Patchnotes.xml +++ b/FoxTube/Assets/Data/Patchnotes.xml @@ -1,5 +1,61 @@  + + + ##[Final pre-release version] +### What's new: +- Fixed fails when trying to retrieve history, WL or recommended +- Fixed ads appearance +- Fixed ads watermarks on video when it was opened through notification +- Optimized and enchanced video playback +- Fixed special characters appearing in toast notifications +- History page re-design +- Added app history management (doesn't affect web site's history) +- Extended history information for videos (watching progress) +- Continue where you left off feature +- Watch later playlist now acts like regular playlist +- If video is longer than 1 hour ads will be shown every 30 minutes +- Added incognito mode (available in video card context menu) +- Search suggestions now run smoother +- 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 +- Added "Auto" quality option for videos +- Updated design of user's avatar in the top-right corner + +####[NB] +This is the final pre-release minor version. That means that until 1.0 release there will be no new features implemented. All subsequent updates will contain only bugfixes + + ##[Последняя предварительная версия] +### Что нового: +- Исправлена проблема получения истории, "Посмотреть позже" и рекомендаций +- Исправлен внешний вид рекламы +- Исправлено появление водяных занков рекламы на видео при открытии через уведомления +- Оптимизирован и улучшен просмотр видео +- Исправлено появление особых символов в уведомлениях +- Редизайн страницы истории +- Добавлено управление историей просмотра приложения (не влияет на историю просмотров на сайте) +- Расширенная информация о просмотренном видео (прогресс просмотра) +- Функция продолжения просмотра +- Плейлист "Посмотреть позже" теперь ведет себя как обычный плейлист +- Если видео длится более 1 часа, рекламный баннер будет появляться каждые 30 минут +- Добавлен режим инкогнито (доступен в контекстном меню видео карточки) +- Подсказки при поиске работают плавнее +- Теперь на кнопке отключения рекламы отображается текущая цена +- Исправлены вылеты при попытке открыть ссылку не содержащую http(s) префикс +- Исправлена обратная навигация при уменьшенном видео +- Редизайн плеера +- Добавлено меню выбора качества для прямых эфиров +- Добавлено опция "Авто" в меню выбора качеста видео +- Обновлен дизайн аватара пользователя в верхнем правом углу + +####[NB] +Версия 0.6 станет последней пред релизной версией. Это значит, что новые функции не будут добовляться до полного релиза приложения. Все последующие обновления будут содержать лишь исправления + + + ### What's new: diff --git a/FoxTube/Classes/ManifestGenerator.cs b/FoxTube/Classes/ManifestGenerator.cs new file mode 100644 index 0000000..0ce6ea5 --- /dev/null +++ b/FoxTube/Classes/ManifestGenerator.cs @@ -0,0 +1,338 @@ +using AngleSharp.Dom.Html; +using AngleSharp.Parser.Html; +using Google.Apis.YouTube.v3.Data; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Xml; +using Windows.ApplicationModel.Resources; +using Windows.Storage; +using YoutubeExplode.Models.MediaStreams; + +namespace FoxTube.Controls.Player +{ + public static class ManifestGenerator + { + static readonly StorageFolder roaming = ApplicationData.Current.RoamingFolder; + public static async Task GetManifest(Video meta, VideoStreamInfo requestedQuality, MediaStreamInfoSet list) + { + StorageFile manifest; + try + { + manifest = await roaming.CreateFileAsync("manifest.mpd", CreationCollisionOption.ReplaceExisting); + } + catch + { + manifest = await roaming.CreateFileAsync("manifest.mpd", CreationCollisionOption.GenerateUniqueName); + } + try + { + + XmlDocument doc = new XmlDocument(); + + XmlElement mpd = doc.CreateElement("MPD"); + mpd.SetAttribute("mediaPresentationDuration", meta.ContentDetails.Duration); + mpd.SetAttribute("minBufferTime", "PT2S"); + mpd.SetAttribute("profiles", "urn:mpeg:dash:profile:isoff-on-demand:2011"); + mpd.SetAttribute("type", "static"); + + XmlElement period = doc.CreateElement("Period"); + + XmlElement videoSet = doc.CreateElement("AdaptationSet"); + XmlElement videoMeta = doc.CreateElement("ContentComponent"); + videoMeta.SetAttribute("contentType", "video"); + videoMeta.SetAttribute("id", "1"); + videoSet.AppendChild(videoMeta); + + StreamInfo streamInfo = await GetInfoAsync(meta, requestedQuality); + + foreach (var i in streamInfo.Video) + videoSet.AppendChild(GetVideoPresentation(doc, i)); + + XmlElement audioSet = doc.CreateElement("AdaptationSet"); + XmlElement audioMeta = doc.CreateElement("ContentComponent"); + audioMeta.SetAttribute("contentType", "audio"); + audioMeta.SetAttribute("id", "2"); + audioSet.AppendChild(audioMeta); + + foreach (var i in streamInfo.Audio) + audioSet.AppendChild(GetAudioPresentation(doc, i)); + + doc.AppendChild(mpd); + mpd.AppendChild(period); + period.AppendChild(videoSet); + period.AppendChild(audioSet); + + doc.Save(await manifest.OpenStreamForWriteAsync()); + + return $"ms-appdata:///roaming/{manifest.Name}".ToUri(); + } + catch (Exception e) + { + return null; + } + } + + private static XmlElement GetVideoPresentation(XmlDocument doc, StreamInfo.VideoInfo info) + { + XmlElement representation = doc.CreateElement("Representation"); + representation.SetAttribute("bandwidth", GetBandwidth(info.Label)); + representation.SetAttribute("id", info.Itag); + representation.SetAttribute("mimeType", info.MimeType); + representation.SetAttribute("codecs", info.Codecs); + representation.SetAttribute("fps", info.Fps); + representation.SetAttribute("height", info.Height); + representation.SetAttribute("width", info.Width); + + XmlElement baseUrl = doc.CreateElement("BaseURL"); + baseUrl.InnerText = info.Url; + representation.AppendChild(baseUrl); + + XmlElement segmentBase = doc.CreateElement("SegmentBase"); + segmentBase.SetAttribute("indexRange", info.IndexRange); + representation.AppendChild(segmentBase); + + XmlElement initialization = doc.CreateElement("Initialization"); + initialization.SetAttribute("range", info.InitRange); + segmentBase.AppendChild(initialization); + + return representation; + } + + private static XmlElement GetAudioPresentation(XmlDocument doc, StreamInfo.AudioInfo info) + { + XmlElement audio = doc.CreateElement("Representation"); + audio.SetAttribute("bandwidth", "200000"); + audio.SetAttribute("id", info.Itag); + audio.SetAttribute("sampleRate", info.SampleRate); + audio.SetAttribute("numChannels", info.ChannelsCount); + audio.SetAttribute("codecs", info.Codecs); + audio.SetAttribute("mimeType", info.MimeType); + + XmlElement audioUrl = doc.CreateElement("BaseURL"); + audioUrl.InnerText = info.Url; + audio.AppendChild(audioUrl); + + XmlElement audioSegmentBase = doc.CreateElement("SegmentBase"); + audioSegmentBase.SetAttribute("indexRange", info.IndexRange); + audio.AppendChild(audioSegmentBase); + + XmlElement audioInit = doc.CreateElement("Initialization"); + audioInit.SetAttribute("range", info.InitRange); + audioSegmentBase.AppendChild(audioInit); + + return audio; + } + + private static async Task GetInfoAsync(Video info, VideoStreamInfo requestedQuality) + { + try + { + StreamInfo si = new StreamInfo(); + HttpClient http = new HttpClient(); + + string response = await http.GetStringAsync($"https://youtube.com/watch?v={info.Id}&disable_polymer=true&bpctr=9999999999&hl=en"); + IHtmlDocument videoEmbedPageHtml = new HtmlParser().Parse(response); + + string playerConfigRaw = Regex.Match(videoEmbedPageHtml.Source.Text, + @"ytplayer\.config = (?\{[^\{\}]*(((?\{)[^\{\}]*)+((?\})[^\{\}]*)+)*(?(Open)(?!))\})") + .Groups["Json"].Value; + JToken playerConfigJson = JToken.Parse(playerConfigRaw); + + var playerResponseRaw = playerConfigJson.SelectToken("args.player_response").Value(); + JToken playerResponseJson = JToken.Parse(playerResponseRaw); + string errorReason = playerResponseJson.SelectToken("playabilityStatus.reason")?.Value(); + if (!string.IsNullOrWhiteSpace(errorReason)) + throw new InvalidDataException($"Video [{info.Id}] is unplayable. Reason: {errorReason}"); + + List> adaptiveStreamInfosUrl = playerConfigJson.SelectToken("args.adaptive_fmts")?.Value().Split(',').Select(SplitQuery).ToList(); + List> video = + requestedQuality == null ? + adaptiveStreamInfosUrl.FindAll(i => i.ContainsKey("quality_label")) : + adaptiveStreamInfosUrl.FindAll(i => i.ContainsValue(requestedQuality.VideoQualityLabel)); + List> audio = adaptiveStreamInfosUrl.FindAll(i => i.ContainsKey("audio_sample_rate")); + + foreach (var i in video) + si.Video.Add(new StreamInfo.VideoInfo + { + IndexRange = i["index"], + Url = i["url"], + Itag = i["itag"], + Fps = i["fps"], + Height = i["size"].Split('x')[1], + Width = i["size"].Split('x')[0], + Codecs = i["type"].Split('"')[1], + MimeType = i["type"].Split(';')[0], + Label = i["quality_label"] + }); + + foreach (var i in audio) + si.Audio.Add(new StreamInfo.AudioInfo + { + ChannelsCount = i["audio_channels"], + IndexRange = i["index"], + SampleRate = i["audio_sample_rate"], + Codecs = i["type"].Split('"')[1], + MimeType = i["type"].Split(';')[0], + Url = i["url"], + Itag = i["itag"] + }); + + return si; + } + catch + { + return new StreamInfo(); + } + } + + public static Dictionary SplitQuery(string query) + { + Dictionary dic = new Dictionary(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 string GetBandwidth(string quality) + { + string parsed = quality.Split('p')[0]; + switch (quality) + { + case "4320": + return $"16763040‬"; + case "3072": + return $"11920384"; + case "2880": + return $"11175360"; + case "2160": + return $"8381520"; + case "1440": + return $"5587680‬"; + case "1080": + return $"4190760"; + case "720": + return $"2073921"; + case "480": + return $"869460"; + case "360": + return $"686521"; + case "240": + return $"264835"; + case "144": + default: + return $"100000"; + } + } + + public static async Task> ResolveLiveSteream(string url) + { + List list = new List(); + string playlistRaw = await new HttpClient().GetStringAsync(url); + + List streamsRaw = playlistRaw.Split("#EXT-X-STREAM-INF:").ToList(); + streamsRaw.RemoveAt(0); + List> streams = new List>(); + foreach (string i in streamsRaw) + { + Dictionary item = new Dictionary(); + 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 = ResourceLoader.GetForCurrentView("VideoPage").GetString("/VideoPage/auto"), + Url = url.ToUri() + }); + list.Reverse(); + + return list; + } + + public static async void ClearRoaming() + { + IReadOnlyList 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 string Itag { get; set; } + public string Fps { get; set; } + public string Url { get; set; } + public string Codecs { get; set; } + public string MimeType { get; set; } + public string Height { get; set; } + public string Width { get; set; } + public string Label { get; set; } + } + 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 string Codecs { get; set; } + public string MimeType { get; set; } + public string Url { get; set; } + public string Itag { get; set; } + } + + public List Video { get; } = new List(); + public List Audio { get; } = new List(); + } + + public class StreamQuality + { + public Uri Url { get; set; } + public string Resolution { get; set; } + } +} diff --git a/FoxTube/Classes/Methods.cs b/FoxTube/Classes/Methods.cs index f6ebd82..13d41d0 100644 --- a/FoxTube/Classes/Methods.cs +++ b/FoxTube/Classes/Methods.cs @@ -25,6 +25,51 @@ namespace FoxTube { object Parameter { get; set; } } + + public class HistoryItem + { + public string Id { get; set; } + public TimeSpan LeftOn { get; set; } = TimeSpan.FromSeconds(0); + } + + public static class HistorySet + { + public static List Items { get; set; } = new List(); + + public static void Update(HistoryItem item) + { + if(Items.Exists(i => i.Id == item.Id)) + Items.RemoveAll(i => i.Id == item.Id); + + Items.Insert(0, item); + Save(); + } + + public static void Delete(HistoryItem item) + { + Items.Remove(item); + Save(); + } + + public static void Clear() + { + Items.Clear(); + Save(); + } + + private static void Save() + { + try { ApplicationData.Current.RoamingSettings.Values[$"history-{SecretsVault.AccountId}"] = JsonConvert.SerializeObject(Items); } + catch { } + } + + public static void Load() + { + if (ApplicationData.Current.RoamingSettings.Values[$"history-{SecretsVault.AccountId}"] != null) + Items = JsonConvert.DeserializeObject>(ApplicationData.Current.RoamingSettings.Values[$"history-{SecretsVault.AccountId}"] as string); + } + } + public static class Methods { private static ResourceLoader resources = ResourceLoader.GetForCurrentView("Methods"); @@ -170,8 +215,12 @@ namespace FoxTube { if (link.IsMatch(item)) { + string str = item; + if (!str.Contains("http")) + str = str.Insert(0, "http://"); + Hyperlink hl = new Hyperlink(); - hl.Click += (s, arg) => ProcessLink(item); + hl.Click += (s, arg) => ProcessLink(str); hl.Inlines.Add(new Run { Text = item }); block.Inlines.Add(hl); } diff --git a/FoxTube/Classes/SecretsVault.cs b/FoxTube/Classes/SecretsVault.cs index cc0c68a..8bd72a4 100644 --- a/FoxTube/Classes/SecretsVault.cs +++ b/FoxTube/Classes/SecretsVault.cs @@ -137,6 +137,8 @@ namespace FoxTube }, "user", CancellationToken.None); + + await Credential.RefreshTokenAsync(CancellationToken.None); } catch (AuthenticateException e) { @@ -255,7 +257,7 @@ namespace FoxTube if (!requset.Products["9NP1QK556625"].IsInUserCollection) { AdsDisabled = false; - Purchased?.Invoke(args:false); + Purchased?.Invoke(null, false, requset.Products["9NP1QK556625"].Price.FormattedPrice); } } catch { } diff --git a/FoxTube/Classes/SettingsStorage.cs b/FoxTube/Classes/SettingsStorage.cs index d564172..09197ca 100644 --- a/FoxTube/Classes/SettingsStorage.cs +++ b/FoxTube/Classes/SettingsStorage.cs @@ -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"]; diff --git a/FoxTube/Controls/Adverts/CardAdvert.xaml b/FoxTube/Controls/Adverts/CardAdvert.xaml index c947886..62e4913 100644 --- a/FoxTube/Controls/Adverts/CardAdvert.xaml +++ b/FoxTube/Controls/Adverts/CardAdvert.xaml @@ -29,7 +29,7 @@ @@ -120,47 +124,12 @@ - - + - - diff --git a/FoxTube/Pages/MainPage.xaml.cs b/FoxTube/Pages/MainPage.xaml.cs index 14ede21..b524f0c 100644 --- a/FoxTube/Pages/MainPage.xaml.cs +++ b/FoxTube/Pages/MainPage.xaml.cs @@ -17,6 +17,8 @@ using Windows.UI.Popups; using Windows.Networking.Connectivity; using Windows.ApplicationModel.Resources; using Microsoft.Services.Store.Engagement; +using Windows.UI.Xaml.Shapes; +using Windows.UI.Xaml.Media; namespace FoxTube { @@ -28,7 +30,7 @@ namespace FoxTube bool wasInvoked = false; public static event ObjectEventHandler VideoPageSizeChanged; readonly ResourceLoader resources = ResourceLoader.GetForCurrentView("Main"); - Dictionary headers; + Dictionary headers; public Page PageContent => content.Content as Page; @@ -45,6 +47,7 @@ namespace FoxTube SecretsVault.Purchased += async (sender, e) => { removeAds.Visibility = (e[0] as bool?).Value ? Visibility.Collapsed : Visibility.Visible; + removeAds.Content = $"{resources.GetString("/Main/adsFree/Content")} ({e[1]})"; if (!(bool)e[0]) return; @@ -57,22 +60,16 @@ namespace FoxTube }; SecretsVault.Initialize(); - headers = new Dictionary() + headers = new Dictionary() { - { typeof(Settings), () => Title.Text = resources.GetString("/Main/settings/Content") }, - { typeof(ChannelPage), () => Title.Text = resources.GetString("/Main/channel") }, - { typeof(PlaylistPage), () => Title.Text = resources.GetString("/Main/playlist") }, - { typeof(Search), () => Title.Text = resources.GetString("/Main/searchPlaceholder/PlaceholderText") }, - { typeof(Subscriptions), () => Title.Text = resources.GetString("/Main/subscriptions/Content") }, - { typeof(History), () => - { - if((content.Content as History).id == "HL") - Title.Text = resources.GetString("/Main/history/Content"); - else - Title.Text = resources.GetString("/Main/later/Content"); - } }, - { typeof(Home), () => Title.Text = resources.GetString("/Main/home/Content") }, - { typeof(Downloads), () => Title.Text = resources.GetString("/Main/downloads/Content") } + { typeof(Settings), resources.GetString("/Main/settings/Content") }, + { typeof(ChannelPage), resources.GetString("/Main/channel") }, + { typeof(PlaylistPage), resources.GetString("/Main/playlist") }, + { typeof(Search), resources.GetString("/Main/searchPlaceholder/PlaceholderText") }, + { typeof(Subscriptions), resources.GetString("/Main/subscriptions/Content") }, + { typeof(History), resources.GetString("/Main/history/Content") }, + { typeof(Home), resources.GetString("/Main/home/Content") }, + { typeof(Downloads), resources.GetString("/Main/downloads/Content") } }; if(StoreServicesFeedbackLauncher.IsSupported()) @@ -182,7 +179,7 @@ namespace FoxTube myNameFlyout.Text = SecretsVault.UserInfo.Name; myEmail.Text = SecretsVault.UserInfo.Email; avatarFlyout.ProfilePicture = new BitmapImage(SecretsVault.UserInfo.Picture.ToUri()) { DecodePixelHeight = 65, DecodePixelWidth = 65 }; - (avatar.Content as PersonPicture).ProfilePicture = new BitmapImage(SecretsVault.UserInfo.Picture.ToUri()) { DecodePixelHeight = 25, DecodePixelWidth = 25 }; + ((avatar.Content as Ellipse).Fill as ImageBrush).ImageSource = new BitmapImage(SecretsVault.UserInfo.Picture.ToUri()) { DecodePixelHeight = 25, DecodePixelWidth = 25 }; avatar.Visibility = Visibility.Visible; @@ -200,6 +197,7 @@ namespace FoxTube for (int k = 0; k < SecretsVault.Subscriptions.Count && k < 10; k++) nav.MenuItems.Add(SecretsVault.Subscriptions[k]); } + HistorySet.Load(); break; case false: @@ -283,7 +281,7 @@ namespace FoxTube content.Navigate(typeof(Home)); } - public async void GoToVideo(string id, string playlistId = null) + public async void GoToVideo(string id, string playlistId = null, bool incognito = false) { if (SettingsStorage.CheckConnection) try @@ -342,7 +340,7 @@ namespace FoxTube nav.IsPaneOpen = false; VideoPageSizeChanged?.Invoke(this, true); - videoPlaceholder.Navigate(typeof(VideoPage), new string[2] { id, playlistId }); + videoPlaceholder.Navigate(typeof(VideoPage), new object[3] { id, playlistId, incognito }); Title.Text = resources.GetString("/Main/video"); } @@ -357,6 +355,11 @@ namespace FoxTube content.Navigate(typeof(PlaylistPage), id); } + public void GoToHistory() + { + content.Navigate(typeof(History)); + } + public void GoToDownloads() { content.Navigate(typeof(Downloads)); @@ -372,8 +375,7 @@ namespace FoxTube else (videoPlaceholder.Content as VideoPage).Player.Minimize(); - try { headers[content.SourcePageType](); } - catch { } + Title.Text = headers[content.SourcePageType]; } public void MinimizeVideo() @@ -393,8 +395,7 @@ namespace FoxTube SetNavigationMenu(); - try { headers[content.SourcePageType](); } - catch { } + Title.Text = headers[content.SourcePageType]; } void SetNavigationMenu() @@ -444,9 +445,8 @@ namespace FoxTube VideoPageSizeChanged?.Invoke(this, false); SetNavigationMenu(); - - try { headers[content.SourcePageType](); } - catch { } + + Title.Text = headers[content.SourcePageType]; } private void Search_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args) @@ -455,25 +455,27 @@ namespace FoxTube GoToSearch(new SearchParameters(search.Text)); } - private void Search_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args) + private async void Search_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args) { - //TODO: Make it run async - if (search.Text.Length > 2 && args.Reason == AutoSuggestionBoxTextChangeReason.UserInput) + await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => { - try + if (search.Text.Length > 2 && args.Reason == AutoSuggestionBoxTextChangeReason.UserInput) { - XmlDocument doc = new XmlDocument(); - doc.Load($"http://suggestqueries.google.com/complete/search?ds=yt&client=toolbar&q={search.Text}"); + try + { + XmlDocument doc = new XmlDocument(); + doc.Load($"http://suggestqueries.google.com/complete/search?ds=yt&client=toolbar&q={search.Text}"); - List suggestions = new List(); + List suggestions = new List(); - for (int i = 0; i < doc["toplevel"].ChildNodes.Count && i < 5; i++) - suggestions.Add(doc["toplevel"].ChildNodes[i]["suggestion"].GetAttribute("data")); + for (int i = 0; i < doc["toplevel"].ChildNodes.Count && i < 5; i++) + suggestions.Add(doc["toplevel"].ChildNodes[i]["suggestion"].GetAttribute("data")); - search.ItemsSource = suggestions; + search.ItemsSource = suggestions; + } + catch { search.ItemsSource = new List(); } } - catch { search.ItemsSource = new List(); } - } + }); } void SetNavigationItem(object item) @@ -490,8 +492,7 @@ namespace FoxTube public void Content_Navigated(object sender, NavigationEventArgs e) { - try { headers[e.SourcePageType](); } - catch { } + Title.Text = headers[content.SourcePageType]; if (!wasInvoked) { @@ -517,29 +518,18 @@ namespace FoxTube SetNavigationItem(null); } else if(e.SourcePageType == typeof(History)) - { - if (e.Parameter.ToString() == "HL") - SetNavigationItem(toHistory); - else - SetNavigationItem(toLater); - } + SetNavigationItem(toHistory); else if(e.SourcePageType == typeof(PlaylistPage)) { if (e.Parameter.ToString() == SecretsVault.UserChannel.ContentDetails.RelatedPlaylists.Likes) SetNavigationItem(toLiked); + else if (e.Parameter.Equals("WL")) + SetNavigationItem(toLater); } } else wasInvoked = false; - if(e.SourcePageType == typeof(History)) - { - if(e.Parameter.ToString() == "HL") - Title.Text = resources.GetString("/Main/history/Content"); - else if(e.Parameter.ToString() == "WL") - Title.Text = resources.GetString("/Main/later/Content"); - } - if (content.CanGoBack) nav.IsBackEnabled = true; else @@ -580,11 +570,11 @@ namespace FoxTube if (args.SelectedItem == toHome) content.Navigate(typeof(Home)); else if (args.SelectedItem == toHistory) - content.Navigate(typeof(History), "HL"); + content.Navigate(typeof(History)); else if (args.SelectedItem == toLiked) content.Navigate(typeof(PlaylistPage), SecretsVault.UserChannel.ContentDetails.RelatedPlaylists.Likes); else if (args.SelectedItem == toLater) - content.Navigate(typeof(History), "WL"); + content.Navigate(typeof(PlaylistPage), "WL"); else if (args.SelectedItem == toSubscriptions) content.Navigate(typeof(Subscriptions)); else if (args.SelectedItem == toDownloads) @@ -603,7 +593,7 @@ namespace FoxTube private void Nav_BackRequested(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewBackRequestedEventArgs args) { - if (videoPlaceholder.Content != null) + if (videoPlaceholder.Content != null && videoPlaceholder.Width == double.NaN) { if ((videoPlaceholder.Content as VideoPage).LoadingPage.State != LoadingState.Loaded) CloseVideo(); diff --git a/FoxTube/Pages/PlaylistPage.xaml b/FoxTube/Pages/PlaylistPage.xaml index b4ac536..fb2db57 100644 --- a/FoxTube/Pages/PlaylistPage.xaml +++ b/FoxTube/Pages/PlaylistPage.xaml @@ -58,6 +58,19 @@ + + + + + + + + + + + + + diff --git a/FoxTube/Pages/PlaylistPage.xaml.cs b/FoxTube/Pages/PlaylistPage.xaml.cs index 2e8e04d..d404e62 100644 --- a/FoxTube/Pages/PlaylistPage.xaml.cs +++ b/FoxTube/Pages/PlaylistPage.xaml.cs @@ -4,6 +4,7 @@ using Google.Apis.YouTube.v3.Data; using Microsoft.AppCenter.Analytics; using System; using System.Collections.Generic; +using System.Linq; using Windows.ApplicationModel.DataTransfer; using Windows.ApplicationModel.Resources; using Windows.Foundation; @@ -26,6 +27,7 @@ namespace FoxTube.Pages PlaylistItemsResource.ListRequest request; string token; + int page = 1; public PlaylistPage() { @@ -40,6 +42,8 @@ namespace FoxTube.Pages Parameter = e.Parameter; if (e.Parameter == null) loading.Error("NullReferenceException", "Unable to initialize page. Playlist ID is not stated."); + else if (e.Parameter as string == "WL") + LoadWL(); else Initialize(e.Parameter as string); } @@ -103,6 +107,48 @@ namespace FoxTube.Pages } } + public async void LoadWL() + { + loading.Refresh(); + + try + { + playlistId = "WL"; + share.Visibility = Visibility.Collapsed; + wlAlert.Visibility = Visibility.Visible; + + SecretsVault.WatchLater = await Methods.GetLater(); + + title.Text = ResourceLoader.GetForCurrentView("Main").GetString("/Main/later/Content"); + info.Text = $"{SecretsVault.WatchLater.Count} {ResourceLoader.GetForCurrentView("Playlist").GetString("/Playlist/videos")}"; + description.Text = ""; + + channelName.Text = SecretsVault.UserChannel.Snippet.Title; + + try { avatar.ProfilePicture = new BitmapImage(SecretsVault.UserChannel.Snippet.Thumbnails.Medium.Url.ToUri()) { DecodePixelWidth = 50, DecodePixelHeight = 50 }; } + catch { } + try { thumbnail.Source = new BitmapImage((await new YoutubeExplode.YoutubeClient().GetVideoAsync(SecretsVault.WatchLater.First())).Thumbnails.HighResUrl.ToUri()); } + catch { } + + for (int k = 0; k < 25 && k < SecretsVault.WatchLater.Count; k++) + list.Add(new VideoCard(SecretsVault.WatchLater[k], "WL")); + + if (list.Count >= SecretsVault.WatchLater.Count) + more.Visibility = Visibility.Collapsed; + + loading.Close(); + } + catch (Exception e) + { + loading.Error(e.GetType().ToString(), e.Message); + Analytics.TrackEvent("WL playlist loading error", new Dictionary() + { + { "Exception", e.GetType().ToString() }, + { "Message", e.Message } + }); + } + } + private void toChannel_Click(object sender, RoutedEventArgs e) { Methods.MainPage.GoToChannel(item.Snippet.ChannelId); @@ -134,6 +180,12 @@ namespace FoxTube.Pages private async void ShowMore_Clicked() { + if(playlistId == "WL") + { + MoreWL(); + return; + } + request.PageToken = token; PlaylistItemListResponse response = await request.ExecuteAsync(); @@ -149,6 +201,17 @@ namespace FoxTube.Pages list.Add(new VideoCard(i.ContentDetails.VideoId, playlistId)); } + private void MoreWL() + { + for (int k = 25 * page++; k < 25 * page && k < SecretsVault.WatchLater.Count; k++) + list.Add(new VideoCard(SecretsVault.WatchLater[k], "WL")); + + if (list.Count >= SecretsVault.WatchLater.Count) + more.Visibility = Visibility.Collapsed; + else + more.Complete(); + } + public void DeleteItem(FrameworkElement card) { list.DeleteItem(card); diff --git a/FoxTube/Pages/SettingsPages/General.xaml b/FoxTube/Pages/SettingsPages/General.xaml index 1bd03f3..0123479 100644 --- a/FoxTube/Pages/SettingsPages/General.xaml +++ b/FoxTube/Pages/SettingsPages/General.xaml @@ -29,6 +29,7 @@ + diff --git a/FoxTube/Pages/VideoPage.xaml b/FoxTube/Pages/VideoPage.xaml index ed989d0..c303df7 100644 --- a/FoxTube/Pages/VideoPage.xaml +++ b/FoxTube/Pages/VideoPage.xaml @@ -58,6 +58,7 @@ + + - - + + - - - - - + - - - - - - - - + + + + + + + + - - - - + + - - - - - - + + + diff --git a/Src/FoxTube screenshots/Eng/Dark/Channel_dark_eng.png b/Src/FoxTube screenshots/Eng/Dark/Channel_dark_eng.png index 2ec6082..80c382a 100644 Binary files a/Src/FoxTube screenshots/Eng/Dark/Channel_dark_eng.png and b/Src/FoxTube screenshots/Eng/Dark/Channel_dark_eng.png differ diff --git a/Src/FoxTube screenshots/Eng/Dark/Main_dark_eng.png b/Src/FoxTube screenshots/Eng/Dark/Main_dark_eng.png index 7b5d6b5..67c3563 100644 Binary files a/Src/FoxTube screenshots/Eng/Dark/Main_dark_eng.png and b/Src/FoxTube screenshots/Eng/Dark/Main_dark_eng.png differ diff --git a/Src/FoxTube screenshots/Eng/Dark/Playlist_dark_eng.png b/Src/FoxTube screenshots/Eng/Dark/Playlist_dark_eng.png index 61f73ee..813d69a 100644 Binary files a/Src/FoxTube screenshots/Eng/Dark/Playlist_dark_eng.png and b/Src/FoxTube screenshots/Eng/Dark/Playlist_dark_eng.png differ diff --git a/Src/FoxTube screenshots/Eng/Dark/Stream_dark_eng.png b/Src/FoxTube screenshots/Eng/Dark/Stream_dark_eng.png index 8264569..dac5eba 100644 Binary files a/Src/FoxTube screenshots/Eng/Dark/Stream_dark_eng.png and b/Src/FoxTube screenshots/Eng/Dark/Stream_dark_eng.png differ diff --git a/Src/FoxTube screenshots/Eng/Dark/Video_dark_eng.png b/Src/FoxTube screenshots/Eng/Dark/Video_dark_eng.png index ddcc2f0..f0be7b3 100644 Binary files a/Src/FoxTube screenshots/Eng/Dark/Video_dark_eng.png and b/Src/FoxTube screenshots/Eng/Dark/Video_dark_eng.png differ diff --git a/Src/FoxTube screenshots/Eng/Light/Channel_light_eng.png b/Src/FoxTube screenshots/Eng/Light/Channel_light_eng.png index 3f08c2e..9671340 100644 Binary files a/Src/FoxTube screenshots/Eng/Light/Channel_light_eng.png and b/Src/FoxTube screenshots/Eng/Light/Channel_light_eng.png differ diff --git a/Src/FoxTube screenshots/Eng/Light/Home_light_eng.png b/Src/FoxTube screenshots/Eng/Light/Home_light_eng.png index 7fb22ed..a271117 100644 Binary files a/Src/FoxTube screenshots/Eng/Light/Home_light_eng.png and b/Src/FoxTube screenshots/Eng/Light/Home_light_eng.png differ diff --git a/Src/FoxTube screenshots/Eng/Light/Playlist_light_eng.png b/Src/FoxTube screenshots/Eng/Light/Playlist_light_eng.png index 18090f0..afd01cd 100644 Binary files a/Src/FoxTube screenshots/Eng/Light/Playlist_light_eng.png and b/Src/FoxTube screenshots/Eng/Light/Playlist_light_eng.png differ diff --git a/Src/FoxTube screenshots/Eng/Light/Stream_light_eng.png b/Src/FoxTube screenshots/Eng/Light/Stream_light_eng.png index 8e29581..74306c1 100644 Binary files a/Src/FoxTube screenshots/Eng/Light/Stream_light_eng.png and b/Src/FoxTube screenshots/Eng/Light/Stream_light_eng.png differ diff --git a/Src/FoxTube screenshots/Eng/Light/Video_light_eng.png b/Src/FoxTube screenshots/Eng/Light/Video_light_eng.png index c2347df..26c3af4 100644 Binary files a/Src/FoxTube screenshots/Eng/Light/Video_light_eng.png and b/Src/FoxTube screenshots/Eng/Light/Video_light_eng.png differ diff --git a/Src/FoxTube screenshots/Eng/Pip_eng.png b/Src/FoxTube screenshots/Eng/Pip_eng.png index c0fabd4..6b3f4a6 100644 Binary files a/Src/FoxTube screenshots/Eng/Pip_eng.png and b/Src/FoxTube screenshots/Eng/Pip_eng.png differ diff --git a/Src/FoxTube screenshots/Rus/Dark/Channel_dark_rus.png b/Src/FoxTube screenshots/Rus/Dark/Channel_dark_rus.png index 0fb15b4..29028c4 100644 Binary files a/Src/FoxTube screenshots/Rus/Dark/Channel_dark_rus.png and b/Src/FoxTube screenshots/Rus/Dark/Channel_dark_rus.png differ diff --git a/Src/FoxTube screenshots/Rus/Dark/Home_dark_rus.png b/Src/FoxTube screenshots/Rus/Dark/Home_dark_rus.png index 2c13e2c..761359c 100644 Binary files a/Src/FoxTube screenshots/Rus/Dark/Home_dark_rus.png and b/Src/FoxTube screenshots/Rus/Dark/Home_dark_rus.png differ diff --git a/Src/FoxTube screenshots/Rus/Dark/Playlist_dark_rus.png b/Src/FoxTube screenshots/Rus/Dark/Playlist_dark_rus.png index 1f2d332..4abc12c 100644 Binary files a/Src/FoxTube screenshots/Rus/Dark/Playlist_dark_rus.png and b/Src/FoxTube screenshots/Rus/Dark/Playlist_dark_rus.png differ diff --git a/Src/FoxTube screenshots/Rus/Dark/Stream_dark_rus.png b/Src/FoxTube screenshots/Rus/Dark/Stream_dark_rus.png index 346b370..2eaa262 100644 Binary files a/Src/FoxTube screenshots/Rus/Dark/Stream_dark_rus.png and b/Src/FoxTube screenshots/Rus/Dark/Stream_dark_rus.png differ diff --git a/Src/FoxTube screenshots/Rus/Dark/Video_dark_rus.png b/Src/FoxTube screenshots/Rus/Dark/Video_dark_rus.png index eab7011..36ab600 100644 Binary files a/Src/FoxTube screenshots/Rus/Dark/Video_dark_rus.png and b/Src/FoxTube screenshots/Rus/Dark/Video_dark_rus.png differ diff --git a/Src/FoxTube screenshots/Rus/Light/Channel_light_rus.png b/Src/FoxTube screenshots/Rus/Light/Channel_light_rus.png index 6293762..75b03fe 100644 Binary files a/Src/FoxTube screenshots/Rus/Light/Channel_light_rus.png and b/Src/FoxTube screenshots/Rus/Light/Channel_light_rus.png differ diff --git a/Src/FoxTube screenshots/Rus/Light/Home_light_rus.png b/Src/FoxTube screenshots/Rus/Light/Home_light_rus.png index eb97fb5..1b0c61c 100644 Binary files a/Src/FoxTube screenshots/Rus/Light/Home_light_rus.png and b/Src/FoxTube screenshots/Rus/Light/Home_light_rus.png differ diff --git a/Src/FoxTube screenshots/Rus/Light/Playlist_light_rus.png b/Src/FoxTube screenshots/Rus/Light/Playlist_light_rus.png index 96d3de3..98d97db 100644 Binary files a/Src/FoxTube screenshots/Rus/Light/Playlist_light_rus.png and b/Src/FoxTube screenshots/Rus/Light/Playlist_light_rus.png differ diff --git a/Src/FoxTube screenshots/Rus/Light/Stream_light_rus.png b/Src/FoxTube screenshots/Rus/Light/Stream_light_rus.png index 6f63caf..86cf3c4 100644 Binary files a/Src/FoxTube screenshots/Rus/Light/Stream_light_rus.png and b/Src/FoxTube screenshots/Rus/Light/Stream_light_rus.png differ diff --git a/Src/FoxTube screenshots/Rus/Light/Video_light_rus.png b/Src/FoxTube screenshots/Rus/Light/Video_light_rus.png index f0ce71e..dfda59d 100644 Binary files a/Src/FoxTube screenshots/Rus/Light/Video_light_rus.png and b/Src/FoxTube screenshots/Rus/Light/Video_light_rus.png differ diff --git a/Src/FoxTube screenshots/Rus/Pip_rus.png b/Src/FoxTube screenshots/Rus/Pip_rus.png index efd2c4a..e74e2cc 100644 Binary files a/Src/FoxTube screenshots/Rus/Pip_rus.png and b/Src/FoxTube screenshots/Rus/Pip_rus.png differ