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 9c72cdf..f8c246f 100644
--- a/FoxTube/Assets/Data/Patchnotes.xml
+++ b/FoxTube/Assets/Data/Patchnotes.xml
@@ -19,6 +19,8 @@
- FoxTube pro price is now displayed in menu
- Fixed crashes on opening links which don't contain http(s) prefix
- Fixed backward navigation with minimized video
+- Player re-design
+- Added quality selector to live streams playback
### Что нового:
- Исправлена проблема получения истории, "Посмотреть позже" и рекомендаций
@@ -37,6 +39,8 @@
- Теперь на кнопке отключения рекламы отображается текущая цена
- Исправлены вылеты при попытке открыть ссылку не содержащую http(s) префикс
- Исправлена обратная навигация при уменьшенном видео
+- Редизайн плеера
+- Добавлено меню выбора качества для прямых эфиров
diff --git a/FoxTube/Classes/ManifestGenerator.cs b/FoxTube/Classes/ManifestGenerator.cs
index 53f5641..6f36f2f 100644
--- a/FoxTube/Classes/ManifestGenerator.cs
+++ b/FoxTube/Classes/ManifestGenerator.cs
@@ -1,26 +1,42 @@
-using System;
-using System.Threading.Tasks;
-using YoutubeExplode.Models.MediaStreams;
-using Windows.Storage;
+using AngleSharp.Dom.Html;
+using AngleSharp.Parser.Html;
using Google.Apis.YouTube.v3.Data;
-using System.Xml;
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
using System.IO;
using System.Linq;
-using System.Collections.Generic;
+using System.Net;
+using System.Net.Http;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using System.Web;
+using System.Xml;
+using Windows.Storage;
+using YoutubeExplode.Models.MediaStreams;
namespace FoxTube.Controls.Player
{
public static class ManifestGenerator
{
- static StorageFolder roaming = ApplicationData.Current.RoamingFolder;
+ static readonly StorageFolder roaming = ApplicationData.Current.RoamingFolder;
public static async Task GetManifest(Video meta, VideoStreamInfo requestedQuality, MediaStreamInfoSet list)
{
- StorageFile manifest = await roaming.CreateFileAsync("manifest.mpd", CreationCollisionOption.ReplaceExisting);
+ StorageFile manifest;
+ try
+ {
+ manifest = await roaming.CreateFileAsync("manifest.mpd", CreationCollisionOption.ReplaceExisting);
+ }
+ catch
+ {
+ manifest = await roaming.CreateFileAsync("manifest.mpd", CreationCollisionOption.GenerateUniqueName);
+ }
XmlDocument doc = new XmlDocument();
XmlElement mpd = doc.CreateElement("MPD");
mpd.SetAttribute("mediaPresentationDuration", meta.ContentDetails.Duration);
+ mpd.SetAttribute("minBufferTime", "PT2S");
XmlElement period = doc.CreateElement("Period");
period.SetAttribute("duration", meta.ContentDetails.Duration);
@@ -31,7 +47,29 @@ namespace FoxTube.Controls.Player
videoMeta.SetAttribute("contentType", "video");
videoSet.AppendChild(videoMeta);
- AppendVideoSet(doc, videoSet, list.Video);
+ StreamInfo streamInfo = await GetInfoAsync(meta, requestedQuality, list);
+
+ XmlElement representation = doc.CreateElement("Representation");
+ representation.SetAttribute("bandwidth", GetBandwidth(requestedQuality.VideoQuality));
+ representation.SetAttribute("height", requestedQuality.Resolution.Height.ToString());
+ representation.SetAttribute("width", requestedQuality.Resolution.Width.ToString());
+ representation.SetAttribute("id", "1");
+ representation.SetAttribute("codecs", requestedQuality.VideoEncoding.ToString());
+ representation.SetAttribute("mimeType", $"video/{requestedQuality.Container.GetFileExtension()}");
+
+ XmlElement baseUrl = doc.CreateElement("BaseURL");
+ baseUrl.InnerText = requestedQuality.Url;
+ representation.AppendChild(baseUrl);
+
+ XmlElement segmentBase = doc.CreateElement("SegmentBase");
+ segmentBase.SetAttribute("indexRange", streamInfo.Video.IndexRange);
+ representation.AppendChild(segmentBase);
+
+ XmlElement initialization = doc.CreateElement("Initialization");
+ initialization.SetAttribute("range", streamInfo.Video.InitRange);
+ segmentBase.AppendChild(initialization);
+
+ videoSet.AppendChild(representation);
XmlElement audioSet = doc.CreateElement("AdaptationSet");
XmlElement audioMeta = doc.CreateElement("ContentComponent");
@@ -39,15 +77,26 @@ namespace FoxTube.Controls.Player
audioSet.AppendChild(audioMeta);
XmlElement audio = doc.CreateElement("Representation");
- audio.SetAttribute("bandwidth", "100000");
- audio.SetAttribute("id", (list.Video.Count + 1).ToString());
- audio.SetAttribute("mimeType", $"audio/{list.Audio.First().Container.GetFileExtension()}");
+ audio.SetAttribute("bandwidth", "200000");
+ audio.SetAttribute("id", "2");
+ audio.SetAttribute("sampleRate", streamInfo.Audio.SampleRate);
+ audio.SetAttribute("numChannels", streamInfo.Audio.ChannelsCount);
+ audio.SetAttribute("codecs", list.Audio.First(i => i.Container.GetFileExtension() == "webm").AudioEncoding.ToString());
+ audio.SetAttribute("mimeType", $"audio/{list.Audio.First(i => i.Container.GetFileExtension() == "webm").Container.GetFileExtension()}");
+ audioSet.AppendChild(audio);
XmlElement audioUrl = doc.CreateElement("BaseURL");
- audioUrl.InnerText = list.Audio.First().Url;
-
+ audioUrl.InnerText = list.Audio.First(i => i.Container.GetFileExtension() == "webm").Url;
audio.AppendChild(audioUrl);
- audioSet.AppendChild(audio);
+
+ XmlElement audioSegmentBase = doc.CreateElement("SegmentBase");
+ audioSegmentBase.SetAttribute("indexRange", streamInfo.Audio.IndexRange);
+ audioSegmentBase.SetAttribute("indexRangeExact", "true");
+ audio.AppendChild(audioSegmentBase);
+
+ XmlElement audioInit = doc.CreateElement("Initialization");
+ audioInit.SetAttribute("range", streamInfo.Audio.InitRange);
+ audioSegmentBase.AppendChild(audioInit);
doc.AppendChild(mpd);
mpd.AppendChild(period);
@@ -56,10 +105,69 @@ namespace FoxTube.Controls.Player
doc.Save(await manifest.OpenStreamForWriteAsync());
- return "ms-appdata:///roaming/manifest.mpd".ToUri();
+ //TODO: Fix this shit. It doesn't work
+ return $"ms-appdata:///roaming/{manifest.Name}".ToUri();
}
- private static void AppendVideoSet(XmlDocument doc, XmlElement root, IReadOnlyList list)
+ private static async Task GetInfoAsync(Video info, VideoStreamInfo requestedQuality, MediaStreamInfoSet list)
+ {
+ HttpClient http = new HttpClient();
+ string response = HttpUtility.HtmlDecode(await http.GetStringAsync($"https://youtube.com/embed/{info.Id}?disable_polymer=true&hl=en"));
+ IHtmlDocument videoEmbedPageHtml = new HtmlParser().Parse(response);
+
+ string playerConfigRaw = Regex.Match(videoEmbedPageHtml.Source.Text,
+ @"yt\.setConfig\({'PLAYER_CONFIG': (?\{[^\{\}]*(((?\{)[^\{\}]*)+((?\})[^\{\}]*)+)*(?(Open)(?!))\})")
+ .Groups["Json"].Value;
+ JToken playerConfigJson = JToken.Parse(playerConfigRaw);
+ string sts = playerConfigJson.SelectToken("sts").Value();
+
+ string eurl = WebUtility.UrlEncode($"https://youtube.googleapis.com/v/{info.Id}");
+ string url = $"https://youtube.com/get_video_info?video_id={info.Id}&el=embedded&sts={sts}&eurl={eurl}&hl=en";
+ string raw = await http.GetStringAsync(url);
+
+ Dictionary videoInfoDic = SplitQuery(raw);
+
+ StreamInfo si = new StreamInfo();
+
+ List> adaptiveStreamInfosUrl = videoInfoDic.GetValueOrDefault("adaptive_fmts").Split(',').Select(SplitQuery).ToList();
+ Dictionary video = adaptiveStreamInfosUrl.Find(i => i["quality_label"] == requestedQuality.VideoQualityLabel && i["type"].Contains(requestedQuality.Container.GetFileExtension()));
+ Dictionary audio = adaptiveStreamInfosUrl.Find(i => i.ContainsKey("audio_sample_rate") && i["type"].Contains("webm"));
+
+ si.Video.IndexRange = video["index"];
+ si.Audio.ChannelsCount = audio["audio_channels"];
+ si.Audio.IndexRange = audio["index"];
+ si.Audio.SampleRate = audio["audio_sample_rate"];
+
+ return si;
+ }
+
+ public static Dictionary 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 void AppendVideoSet(XmlDocument doc, XmlElement root, List list)
{
for (int k = 0; k < list.Count; k++)
{
@@ -87,7 +195,7 @@ namespace FoxTube.Controls.Player
private static string GetBandwidth(VideoQuality quality)
{
- switch(quality)
+ switch (quality)
{
case VideoQuality.High4320:
return $"16763040";
@@ -114,5 +222,79 @@ namespace FoxTube.Controls.Player
return $"100000";
}
}
+
+ public static async Task> 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 = "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 class AudioInfo
+ {
+ public string IndexRange { get; set; }
+ public string InitRange => $"0-{int.Parse(IndexRange.Split('-')[0]) - 1}";
+ public string SampleRate { get; set; }
+ public string ChannelsCount { get; set; }
+ }
+
+ public VideoInfo Video { get; } = new VideoInfo();
+ public AudioInfo Audio { get; } = new AudioInfo();
+ }
+
+ public class StreamQuality
+ {
+ public Uri Url { get; set; }
+ public string Resolution { get; set; }
}
}
diff --git a/FoxTube/Classes/Methods.cs b/FoxTube/Classes/Methods.cs
index 32a5349..13d41d0 100644
--- a/FoxTube/Classes/Methods.cs
+++ b/FoxTube/Classes/Methods.cs
@@ -59,7 +59,8 @@ namespace FoxTube
private static void Save()
{
- ApplicationData.Current.RoamingSettings.Values[$"history-{SecretsVault.AccountId}"] = JsonConvert.SerializeObject(Items);
+ try { ApplicationData.Current.RoamingSettings.Values[$"history-{SecretsVault.AccountId}"] = JsonConvert.SerializeObject(Items); }
+ catch { }
}
public static void Load()
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/CommentCard.xaml.cs b/FoxTube/Controls/CommentCard.xaml.cs
index 9c78022..9924f85 100644
--- a/FoxTube/Controls/CommentCard.xaml.cs
+++ b/FoxTube/Controls/CommentCard.xaml.cs
@@ -52,21 +52,23 @@ namespace FoxTube.Controls
else
rating.Text = comment.Snippet.TopLevelComment.Snippet.LikeCount.HasValue ? comment.Snippet.TopLevelComment.Snippet.LikeCount.ToString() : "";
- if (item.Snippet.AuthorChannelId.ToString().Split('"')[3] == SecretsVault.AccountId)
+ if(item.Snippet.AuthorChannelId != null)
{
- (specialAuthor.Child as TextBlock).Text = comment.Snippet.TopLevelComment.Snippet.AuthorDisplayName;
- specialAuthor.Visibility = Visibility.Visible;
- author.Visibility = Visibility.Collapsed;
- editBtn.Visibility = Visibility.Visible;
+ if (item.Snippet.AuthorChannelId.ToString().Split('"')[3] == SecretsVault.AccountId)
+ {
+ (specialAuthor.Child as TextBlock).Text = comment.Snippet.TopLevelComment.Snippet.AuthorDisplayName;
+ specialAuthor.Visibility = Visibility.Visible;
+ author.Visibility = Visibility.Collapsed;
+ editBtn.Visibility = Visibility.Visible;
+ }
+ else if (item.Snippet.AuthorChannelId.ToString().Split('"')[3] == item.Snippet.ChannelId)
+ {
+ (specialAuthor.Child as TextBlock).Text = comment.Snippet.TopLevelComment.Snippet.AuthorDisplayName;
+ specialAuthor.Visibility = Visibility.Visible;
+ author.Visibility = Visibility.Collapsed;
+ }
}
- else if (item.Snippet.AuthorChannelId.ToString().Split('"')[3] == item.Snippet.ChannelId)
- {
- (specialAuthor.Child as TextBlock).Text = comment.Snippet.TopLevelComment.Snippet.AuthorDisplayName;
- specialAuthor.Visibility = Visibility.Visible;
- author.Visibility = Visibility.Collapsed;
- }
- else
- author.Text = comment.Snippet.TopLevelComment.Snippet.AuthorDisplayName;
+ author.Text = comment.Snippet.TopLevelComment.Snippet.AuthorDisplayName;
meta.Text = string.Format("{0} {1}", Methods.GetAgo(comment.Snippet.TopLevelComment.Snippet.PublishedAt.Value), comment.Snippet.TopLevelComment.Snippet.UpdatedAt != comment.Snippet.TopLevelComment.Snippet.PublishedAt ? resources.GetString("/CommentsPage/edited") : "");
Methods.FormatText(ref text, comment.Snippet.TopLevelComment.Snippet.TextDisplay);
diff --git a/FoxTube/Controls/LiveCaptions.xaml.cs b/FoxTube/Controls/LiveCaptions.xaml.cs
index 734d2cb..230af80 100644
--- a/FoxTube/Controls/LiveCaptions.xaml.cs
+++ b/FoxTube/Controls/LiveCaptions.xaml.cs
@@ -17,7 +17,9 @@ namespace FoxTube.Controls
set => text.FontSize = value;
}
- public MediaPlayer Player { get; set; }
+ public bool IsActive => track != null;
+
+ public MediaElement Player { get; set; }
DispatcherTimer timer = new DispatcherTimer() { Interval = TimeSpan.FromMilliseconds(10) };
ClosedCaption currentCaption = null;
@@ -32,7 +34,7 @@ namespace FoxTube.Controls
private void UpdateCaption(object sender, object e)
{
- currentCaption = track.Captions.Find(i => i.Offset <= Player.PlaybackSession.Position && i.Offset + i.Duration > Player.PlaybackSession.Position);
+ currentCaption = track.Captions.Find(i => i.Offset <= Player.Position && i.Offset + i.Duration > Player.Position);
if (currentCaption != null)
text.Text = currentCaption.Text;
@@ -51,10 +53,22 @@ namespace FoxTube.Controls
public void Close()
{
+ timer.Stop();
track = null;
currentCaption = null;
Visibility = Visibility.Collapsed;
- timer.Stop();
+ }
+
+ public void Minimize()
+ {
+ Margin = new Thickness(0, 0, 0, 20);
+ text.FontSize = 15;
+ }
+
+ public void Maximize()
+ {
+ Margin = new Thickness(0, 0, 0, 55);
+ text.FontSize = 24;
}
}
}
diff --git a/FoxTube/Controls/Player/PlayerControls.cs b/FoxTube/Controls/Player/PlayerControls.cs
index 47ca80e..16cc593 100644
--- a/FoxTube/Controls/Player/PlayerControls.cs
+++ b/FoxTube/Controls/Player/PlayerControls.cs
@@ -1,38 +1,111 @@
using FoxTube.Controls;
using FoxTube.Controls.Adverts;
+using FoxTube.Controls.Player;
+using Google.Apis.YouTube.v3.Data;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Windows.ApplicationModel.Resources;
+using Windows.Graphics.Display;
using Windows.Media.Core;
-using Windows.Media.Playback;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
+using YoutubeExplode;
using YoutubeExplode.Models.ClosedCaptions;
using YoutubeExplode.Models.MediaStreams;
namespace FoxTube
{
- public delegate void QualityChangedEventHandler(object sender, MediaStreamInfo requestedQuality, MediaStreamInfoSet list);
public delegate void MinimodeChangedEventHandler(object sender, bool isOn);
+ public enum PlayerDisplayState { Normal, Minimized, Compact }
+
+ public class QualityComparer : IComparer
+ {
+ public int Compare(string x, string y)
+ {
+ string[] xArr = x.Split('p');
+ string[] yArr = y.Split('p');
+
+ int qualityA = int.Parse(xArr[0]);
+ int qualityB = int.Parse(yArr[0]);
+ int framerateA = 30;
+ int framerateB = 30;
+
+ if (!string.IsNullOrWhiteSpace(xArr[1]))
+ framerateA = int.Parse(xArr[1]);
+ if (!string.IsNullOrWhiteSpace(yArr[1]))
+ framerateB = int.Parse(yArr[1]);
+
+ if (qualityA > qualityB)
+ return 1;
+ else if (qualityA < qualityB)
+ return -1;
+ else
+ return framerateA - framerateB > 0 ? 1 : -1;
+ }
+ }
+
///
/// Custom controls for media player. MARKUP IS IN **Themes/Generic.xaml**!!!
///
public sealed class PlayerControls : MediaTransportControls
{
public event RoutedEventHandler CloseRequested;
- public event RoutedEventHandler LiveRequested;
public event MinimodeChangedEventHandler MiniModeChanged;
public event RoutedEventHandler NextRequested;
- public event Event MuteClicked;
- public event QualityChangedEventHandler QualityChanged;
+ #region Controls variables
+ Button minimize;
+ Button close;
+ Button miniview;
+ Button play;
+ Button next;
+ Button volumeMenu;
+ Button mute;
+ Button live;
+ Button fwd;
+ Button bwd;
+ Button captionsMenu;
+ Button settingsMenu;
+ Button fullscreen;
+ Button drag;
- public MediaPlayer Player;
+ TextBlock title;
+ TextBlock channel;
+ TextBlock elapsed;
+ TextBlock remain;
+
+ Slider volume;
+ Slider seek;
+ ProgressBar seekIndicator;
+
+ ComboBox captions;
+ ComboBox quality;
+
+ ToggleSwitch captionsSwitch;
+
+ StackPanel rightFooter;
+ StackPanel leftFooter;
+ StackPanel rightHeader;
+ StackPanel centerStack;
+ Grid header;
+ Grid footer;
+ Grid center;
+ #endregion
+
+ PlayerDisplayState State { get; set; } = PlayerDisplayState.Normal;
+
+ public MediaElement Player { get; set; }
public PlayerAdvert Advert;
+ public LiveCaptions Caption;
+
+ TimeSpan timecodeBackup;
+ bool needUpdateTimecode = false;
+
+ public Video Meta { get; set; }
public IReadOnlyList ClosedCaptions { get; set; }
public MediaStreamInfoSet MediaStreams { get; set; }
@@ -47,31 +120,22 @@ namespace FoxTube
protected override void OnApplyTemplate()
{
+ AssignControls();
+
isReady = true;
- Advert = GetTemplateChild("ad") as PlayerAdvert;
+ minimize.Click += Minimize_Click;
+ close.Click += Close_Click;
+ miniview.Click += Miniview_Click;
- (GetTemplateChild("close") as Button).Click += Close_Click;
- (GetTemplateChild("compactClose") as Button).Click += Close_Click;
+ next.Click += Next_Click;
+ volume.ValueChanged += Volume_ValueChanged;
+ live.Click += Live_Click;
- (GetTemplateChild("minimize") as Button).Click += Minimize_Click;
- (GetTemplateChild("maximize") as Button).Click += Minimize_Click;
-
- (GetTemplateChild("CompactOverlayButton") as Button).Click += CompactOverlay_Click;
-
- (GetTemplateChild("qualitySelector") as ComboBox).SelectionChanged += QualitySelector_SelectionChanged;
-
- (GetTemplateChild("ccSwitch") as ToggleSwitch).Toggled += CcSwitch_Toggled;
- (GetTemplateChild("ccSelector") as ComboBox).SelectionChanged += CcSelector_SelectionChanged;
-
- (GetTemplateChild("next") as Button).Click += (s, e) => NextRequested.Invoke(s, e);
-
- (GetTemplateChild("AudioMuteButton") as Button).Click += Mute_Click;
- (GetTemplateChild("VolumeSlider") as Slider).ValueChanged += Volume_ValueChanged;
-
- (GetTemplateChild("ProgressSlider") as Slider).ValueChanged += ProgressSlider_ValueChanged;
-
- (GetTemplateChild("goLive") as Button).Click += (s, e) => LiveRequested.Invoke(s, e);
+ captionsSwitch.Toggled += CaptionsSwitch_Toggled;
+ captions.SelectionChanged += Captions_SelectionChanged;
+ quality.SelectionChanged += Quality_SelectionChanged;
+ seek.ValueChanged += Seek_ValueChanged;
if (queue.Count > 0)
foreach (Action i in queue)
@@ -80,96 +144,135 @@ namespace FoxTube
base.OnApplyTemplate();
}
- private void Minimize_Click(object sender, RoutedEventArgs e)
+ void AssignControls()
{
- if (sender == (GetTemplateChild("minimize") as Button))
+ minimize = GetTemplateChild("MinimizeButton") as Button;
+ close = GetTemplateChild("CloseButton") as Button;
+ miniview = GetTemplateChild("CompactOverlayButton") as Button;
+ play = GetTemplateChild("PlayPauseButton") as Button;
+ next = GetTemplateChild("NextButton") as Button;
+ volumeMenu = GetTemplateChild("VolumeMenuButton") as Button;
+ mute = GetTemplateChild("AudioMuteButton") as Button;
+ live = GetTemplateChild("PlayLiveButton") as Button;
+ fwd = GetTemplateChild("SkipForwardButton") as Button;
+ bwd = GetTemplateChild("SkipBackwardButton") as Button;
+ captionsMenu = GetTemplateChild("CaptionsMenuButton") as Button;
+ settingsMenu = GetTemplateChild("QualityMenuButton") as Button;
+ fullscreen = GetTemplateChild("FullWindowButton") as Button;
+ drag = GetTemplateChild("drag") as Button;
+
+ Advert = GetTemplateChild("AdvertControl") as PlayerAdvert;
+ Caption = GetTemplateChild("CaptionControl") as LiveCaptions;
+
+ title = GetTemplateChild("title") as TextBlock;
+ channel = GetTemplateChild("channel") as TextBlock;
+ elapsed = GetTemplateChild("TimeElapsedElement") as TextBlock;
+ remain = GetTemplateChild("TimeRemainingElement") as TextBlock;
+
+ volume = GetTemplateChild("VolumeSlider") as Slider;
+ seek = GetTemplateChild("ProgressSlider") as Slider;
+ seekIndicator = GetTemplateChild("SeekIndicator") as ProgressBar;
+
+ captions = GetTemplateChild("CaptionsSelector") as ComboBox;
+ quality = GetTemplateChild("QualitySelector") as ComboBox;
+ captionsSwitch = GetTemplateChild("CaptionsToggleSwitch") as ToggleSwitch;
+
+ rightFooter = GetTemplateChild("RightFooterControls") as StackPanel;
+ leftFooter = GetTemplateChild("LeftFooterControls") as StackPanel;
+ rightHeader = GetTemplateChild("RightHeaderControls") as StackPanel;
+ centerStack = GetTemplateChild("centerControls") as StackPanel;
+ header = GetTemplateChild("header") as Grid;
+ footer = GetTemplateChild("footer") as Grid;
+ center = GetTemplateChild("center") as Grid;
+ }
+
+ private void Seek_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
+ {
+ seekIndicator.Value = seek.Value;
+ }
+
+ private async void Quality_SelectionChanged(object sender, SelectionChangedEventArgs e)
+ {
+ if (Meta.Snippet.LiveBroadcastContent == "live")
+ goto SetQuality;
+ if(!needUpdateTimecode)
+ timecodeBackup = Player.Position;
+ needUpdateTimecode = true;
+ Player.Pause();
+ Player.Source = null;
+
+ SetQuality:
+ object info = (quality.SelectedItem as ComboBoxItem).Tag;
+ if (info is MuxedStreamInfo)
+ Player.SetPlaybackSource(MediaSource.CreateFromUri((info as MuxedStreamInfo).Url.ToUri()));
+ else if (info is VideoStreamInfo)
+ Player.SetPlaybackSource(MediaSource.CreateFromUri(await ManifestGenerator.GetManifest(Meta, info as VideoStreamInfo, MediaStreams)));
+ else if (info is StreamQuality)
+ Player.SetPlaybackSource(MediaSource.CreateFromUri((info as StreamQuality).Url));
+ }
+
+ private void Captions_SelectionChanged(object sender, SelectionChangedEventArgs e)
+ {
+ if(Caption.IsActive)
{
- MiniModeChanged.Invoke(this, true);
- SetMinimized();
- }
- else
- {
- MiniModeChanged.Invoke(this, false);
- SetNormal();
+ Caption.Close();
+ Caption.Initialize((captions.SelectedItem as ComboBoxItem).Tag as ClosedCaptionTrackInfo);
}
}
- public void Minimize()
+ private void CaptionsSwitch_Toggled(object sender, RoutedEventArgs e)
{
- Minimize_Click(GetTemplateChild("minimize"), null);
- }
-
- private void ProgressSlider_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
- {
- (GetTemplateChild("compactSeek") as ProgressBar).Value = e.NewValue;
- }
-
- private void Mute_Click(object sender, RoutedEventArgs e)
- {
- if (((GetTemplateChild("AudioMuteButton") as Button).Content as FontIcon).Glyph == "\xE74F")
- Volume_ValueChanged(this, null);
+ if(captionsSwitch.IsOn)
+ Caption.Initialize((captions.SelectedItem as ComboBoxItem).Tag as ClosedCaptionTrackInfo);
else
- ((GetTemplateChild("AudioMuteButton") as Button).Content as FontIcon).Glyph = ((GetTemplateChild("volume") as Button).Content as FontIcon).Glyph = "\xE74F";
+ Caption.Close();
+ }
- MuteClicked?.Invoke();
+ private void Live_Click(object sender, RoutedEventArgs e)
+ {
+ Player.Position = Player.NaturalDuration.TimeSpan;
+ }
+
+ private void Next_Click(object sender, RoutedEventArgs e)
+ {
+ NextRequested.Invoke(sender, e);
+ }
+
+ private void Miniview_Click(object sender, RoutedEventArgs e)
+ {
+ if (State == PlayerDisplayState.Compact)
+ Maximize();
+ else
+ EnterMiniview();
+ }
+
+ public void UpdateVolumeIcon()
+ {
+ Volume_ValueChanged(this, null);
}
private void Volume_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
{
- double v = (GetTemplateChild("VolumeSlider") as Slider).Value;
- if (v == 0)
- ((GetTemplateChild("AudioMuteButton") as Button).Content as FontIcon).Glyph = ((GetTemplateChild("volume") as Button).Content as FontIcon).Glyph = "\xE74F";
+ double v = volume.Value;
+ if (v == 0 || Player.IsMuted)
+ volumeMenu.Content = mute.Content = "\xE74F";
else if (v <= 25 && v > 0)
- ((GetTemplateChild("AudioMuteButton") as Button).Content as FontIcon).Glyph = ((GetTemplateChild("volume") as Button).Content as FontIcon).Glyph = "\xE992";
+ volumeMenu.Content = mute.Content = "\xE992";
else if (v <= 50 && v > 25)
- ((GetTemplateChild("AudioMuteButton") as Button).Content as FontIcon).Glyph = ((GetTemplateChild("volume") as Button).Content as FontIcon).Glyph = "\xE993";
+ volumeMenu.Content = mute.Content = "\xE993";
else if (v <= 75 && v > 50)
- ((GetTemplateChild("AudioMuteButton") as Button).Content as FontIcon).Glyph = ((GetTemplateChild("volume") as Button).Content as FontIcon).Glyph = "\xE994";
+ volumeMenu.Content = mute.Content = "\xE994";
else if (v > 75)
- ((GetTemplateChild("AudioMuteButton") as Button).Content as FontIcon).Glyph = ((GetTemplateChild("volume") as Button).Content as FontIcon).Glyph = "\xE995";
+ volumeMenu.Content = mute.Content = "\xE995";
}
- private void CcSelector_SelectionChanged(object sender, SelectionChangedEventArgs e)
+ private void Player_MediaOpened(object sender, RoutedEventArgs args)
{
- CcSwitch_Toggled((GetTemplateChild("ccSwitch") as ToggleSwitch), null);
- }
+ if (!needUpdateTimecode)
+ return;
- private void CcSwitch_Toggled(object sender, RoutedEventArgs e)
- {
- if((GetTemplateChild("captions") as LiveCaptions).Player == null)
- (GetTemplateChild("captions") as LiveCaptions).Player = Player;
-
- if ((sender as ToggleSwitch).IsOn)
- (GetTemplateChild("captions") as LiveCaptions).Initialize(((GetTemplateChild("ccSelector") as ComboBox).SelectedItem as ComboBoxItem).Tag as ClosedCaptionTrackInfo);
- else
- (GetTemplateChild("captions") as LiveCaptions).Close();
- }
-
- private void QualitySelector_SelectionChanged(object sender, SelectionChangedEventArgs e)
- {
- SettingsStorage.RememberedQuality = e.AddedItems[0] as string;
- MediaStreamInfo item = MediaStreams.Muxed.Find(i => i.VideoQualityLabel.Contains(e.AddedItems[0] as string));
- if (item == null)
- item = MediaStreams.Video.Find(i => i.VideoQualityLabel == e.AddedItems[0] as string);
-
- QualityChanged?.Invoke(sender, item, MediaStreams);
- }
-
- private void CompactOverlay_Click(object sender, RoutedEventArgs e)
- {
- if((sender as Button).Margin.Top > 0)
- {
- Button btnCompact = sender as Button;
- (GetTemplateChild("center") as Grid).Children.Remove(btnCompact);
- (GetTemplateChild("headerToolbar") as StackPanel).Children.Add(btnCompact);
-
- btnCompact.Margin = new Thickness(0);
- (btnCompact.Content as FontIcon).Glyph = "\xE2B3";
-
- SetNormal();
- }
- else
- SetCompactView();
+ needUpdateTimecode = false;
+ Player.Position = timecodeBackup;
}
private void Close_Click(object sender, RoutedEventArgs e)
@@ -177,179 +280,238 @@ namespace FoxTube
CloseRequested?.Invoke(sender, e);
}
- public void SetCompactView()
+ private void Minimize_Click(object sender, RoutedEventArgs e)
{
- Button btn = GetTemplateChild("CompactOverlayButton") as Button;
- (GetTemplateChild("headerToolbar") as StackPanel).Children.Remove(btn);
- (GetTemplateChild("center") as Grid).Children.Add(btn);
-
- btn.Margin = new Thickness(0, 32, 0, 0);
- (btn.Content as FontIcon).Glyph = "\xE2B4";
-
- Button btnPlay = GetTemplateChild("PlayPauseButton") as Button;
- (GetTemplateChild("leftStack") as StackPanel).Children.Remove(btnPlay);
- (GetTemplateChild("centralStack") as StackPanel).Children.Add(btnPlay);
-
- Button btnFwd = GetTemplateChild("SkipForwardButton") as Button;
- (GetTemplateChild("rightStack") as StackPanel).Children.Remove(btnFwd);
- (GetTemplateChild("centralStack") as StackPanel).Children.Insert(1, btnFwd);
-
- Button btnBwd = GetTemplateChild("SkipBackwardButton") as Button;
- (GetTemplateChild("rightStack") as StackPanel).Children.Remove(btnBwd);
- (GetTemplateChild("centralStack") as StackPanel).Children.Insert(0, btnBwd);
-
- (GetTemplateChild("header") as Grid).Visibility = Visibility.Collapsed;
- (GetTemplateChild("centerBgr") as Grid).Visibility = Visibility.Visible;
- (GetTemplateChild("center") as Grid).Visibility = Visibility.Visible;
- (GetTemplateChild("footer") as Grid).Visibility = Visibility.Collapsed;
-
- (GetTemplateChild("maximize") as Button).Visibility = Visibility.Collapsed;
- (GetTemplateChild("compactClose") as Button).Visibility = Visibility.Collapsed;
-
- (GetTemplateChild("dragholder") as Button).Visibility = Visibility.Visible;
-
- (GetTemplateChild("captions") as LiveCaptions).Size = 15;
-
- (GetTemplateChild("root") as Grid).RowDefinitions[1].Height = new GridLength(0);
+ if (State == PlayerDisplayState.Normal)
+ Minimize();
+ else
+ Maximize();
}
- public void SetMinimized()
+ public void Minimize()
{
- Button btnPlay = GetTemplateChild("PlayPauseButton") as Button;
- (GetTemplateChild("leftStack") as StackPanel).Children.Remove(btnPlay);
- (GetTemplateChild("centralStack") as StackPanel).Children.Add(btnPlay);
-
- Button btnFwd = GetTemplateChild("SkipForwardButton") as Button;
- (GetTemplateChild("rightStack") as StackPanel).Children.Remove(btnFwd);
- (GetTemplateChild("centralStack") as StackPanel).Children.Insert(1, btnFwd);
-
- Button btnBwd = GetTemplateChild("SkipBackwardButton") as Button;
- (GetTemplateChild("rightStack") as StackPanel).Children.Remove(btnBwd);
- (GetTemplateChild("centralStack") as StackPanel).Children.Insert(0, btnBwd);
-
- (GetTemplateChild("header") as Grid).Visibility = Visibility.Collapsed;
- (GetTemplateChild("centerBgr") as Grid).Visibility = Visibility.Visible;
- (GetTemplateChild("center") as Grid).Visibility = Visibility.Visible;
- (GetTemplateChild("footer") as Grid).Visibility = Visibility.Collapsed;
-
- (GetTemplateChild("compactClose") as Button).Visibility = Visibility.Visible;
- (GetTemplateChild("maximize") as Button).Visibility = Visibility.Visible;
-
- (GetTemplateChild("captions") as LiveCaptions).Size = 15;
-
- (GetTemplateChild("root") as Grid).RowDefinitions[1].Height = new GridLength(0);
- }
-
- public void SetNormal()
- {
- Button btnPlay = GetTemplateChild("PlayPauseButton") as Button;
- (GetTemplateChild("centralStack") as StackPanel).Children.Remove(btnPlay);
- (GetTemplateChild("leftStack") as StackPanel).Children.Insert(0, btnPlay);
-
- Button btnFwd = GetTemplateChild("SkipForwardButton") as Button;
- (GetTemplateChild("centralStack") as StackPanel).Children.Remove(btnFwd);
- (GetTemplateChild("rightStack") as StackPanel).Children.Insert(0, btnFwd);
-
- Button btnBwd = GetTemplateChild("SkipBackwardButton") as Button;
- (GetTemplateChild("centralStack") as StackPanel).Children.Remove(btnBwd);
- (GetTemplateChild("rightStack") as StackPanel).Children.Insert(0, btnBwd);
-
- (GetTemplateChild("header") as Grid).Visibility = Visibility.Visible;
- (GetTemplateChild("centerBgr") as Grid).Visibility = Visibility.Collapsed;
- (GetTemplateChild("center") as Grid).Visibility = Visibility.Collapsed;
- (GetTemplateChild("footer") as Grid).Visibility = Visibility.Visible;
-
- (GetTemplateChild("captions") as LiveCaptions).Size = 24;
-
- (GetTemplateChild("dragholder") as Button).Visibility = Visibility.Collapsed;
-
- (GetTemplateChild("root") as Grid).RowDefinitions[1].Height = new GridLength(1, GridUnitType.Auto);
- }
-
- public void SetMeta(string title, string channel)
- {
- if (!isReady)
- {
- queue.Enqueue(() => SetMeta(title, channel));
+ if (State == PlayerDisplayState.Minimized)
return;
- }
- (GetTemplateChild("title") as TextBlock).Text = title;
- (GetTemplateChild("author") as TextBlock).Text = channel;
+
+ header.Children.Remove(minimize);
+ center.Children.Add(minimize);
+ rightHeader.Children.Remove(close);
+ center.Children.Add(close);
+ leftFooter.Children.Remove(play);
+ centerStack.Children.Add(play);
+ rightFooter.Children.Remove(fwd);
+ centerStack.Children.Add(fwd);
+ rightFooter.Children.Remove(bwd);
+ centerStack.Children.Insert(0, bwd);
+
+ header.Visibility = Visibility.Collapsed;
+ center.Visibility = Visibility.Visible;
+ footer.Visibility = Visibility.Collapsed;
+
+ minimize.Content = "\xE010";
+
+ MiniModeChanged.Invoke(this, true);
+ Caption.Minimize();
+
+ State = PlayerDisplayState.Minimized;
}
- public void SetCaptions(IReadOnlyList list)
+ public void Maximize()
{
- if (!isReady)
+ if (State == PlayerDisplayState.Normal)
+ return;
+
+ if(State == PlayerDisplayState.Compact)
{
- queue.Enqueue(() => SetCaptions(list));
+ center.Children.Remove(miniview);
+ rightHeader.Children.Add(miniview);
+ centerStack.Children.Remove(play);
+ leftFooter.Children.Insert(0, play);
+ centerStack.Children.Remove(fwd);
+ rightFooter.Children.Insert(0, fwd);
+ centerStack.Children.Remove(bwd);
+ rightFooter.Children.Insert(0, bwd);
+
+ miniview.Margin = new Thickness();
+ miniview.Width = 50;
+ miniview.Height = 50;
+ }
+ else
+ {
+ center.Children.Remove(minimize);
+ header.Children.Insert(0, minimize);
+ center.Children.Remove(close);
+ rightHeader.Children.Insert(0, close);
+ centerStack.Children.Remove(play);
+ leftFooter.Children.Insert(0, play);
+ centerStack.Children.Remove(fwd);
+ rightFooter.Children.Insert(0, fwd);
+ centerStack.Children.Remove(bwd);
+ rightFooter.Children.Insert(0, bwd);
+
+ MiniModeChanged.Invoke(this, false);
+ }
+
+ drag.Visibility = Visibility.Collapsed;
+ header.Visibility = Visibility.Visible;
+ center.Visibility = Visibility.Collapsed;
+ footer.Visibility = Visibility.Visible;
+ miniview.Content = "\xE2B3";
+ minimize.Content = "\xE011";
+ Caption.Maximize();
+
+ State = PlayerDisplayState.Normal;
+ }
+
+ public void EnterMiniview()
+ {
+ if (State == PlayerDisplayState.Compact)
+ return;
+
+ rightHeader.Children.Remove(miniview);
+ center.Children.Add(miniview);
+ leftFooter.Children.Remove(play);
+ centerStack.Children.Add(play);
+ rightFooter.Children.Remove(fwd);
+ centerStack.Children.Add(fwd);
+ rightFooter.Children.Remove(bwd);
+ centerStack.Children.Insert(0, bwd);
+
+ drag.Visibility = Visibility.Visible;
+ header.Visibility = Visibility.Collapsed;
+ center.Visibility = Visibility.Visible;
+ footer.Visibility = Visibility.Collapsed;
+
+ miniview.Margin = new Thickness(0, 32, 0, 0);
+ miniview.Width = 47;
+ miniview.Height = 47;
+
+ miniview.Content = "\xE2B4";
+ Caption.Minimize();
+
+ State = PlayerDisplayState.Compact;
+ }
+
+ public async void Load(Video meta)
+ {
+ if(!isReady)
+ {
+ queue.Enqueue(() => Load(meta));
return;
}
- ClosedCaptions = list;
+ Player.MediaOpened += Player_MediaOpened;
- if (list.Count > 0)
+ Meta = meta;
+ title.Text = meta.Snippet.Title;
+ channel.Text = meta.Snippet.ChannelTitle;
+
+ MediaStreams = await new YoutubeClient().GetVideoMediaStreamInfosAsync(meta.Id);
+
+ if (meta.Snippet.LiveBroadcastContent == "none")
{
- foreach(ClosedCaptionTrackInfo i in list)
- (GetTemplateChild("ccSelector") as ComboBox).Items.Add(new ComboBoxItem()
+ ClosedCaptions = await new YoutubeClient().GetVideoClosedCaptionTrackInfosAsync(meta.Id);
+
+ /*foreach (MuxedStreamInfo i in MediaStreams.Muxed)
+ quality.Items.Add(new ComboBoxItem
+ {
+ Content = $"{i.VideoQualityLabel} (muxed)",
+ Tag = i
+ });
+ foreach (VideoStreamInfo i in MediaStreams.Video)
+ quality.Items.Add(new ComboBoxItem
+ {
+ Content = $"{i.VideoQualityLabel} (video-only)",
+ Tag = i
+ });
+ foreach (AudioStreamInfo i in MediaStreams.Audio)
+ quality.Items.Add(new ComboBoxItem
+ {
+ Content = $"{i.Bitrate} (audio-only)",
+ Tag = i
+ });*/
+
+ uint screenHeight = DisplayInformation.GetForCurrentView().ScreenHeightInRawPixels;
+
+ List qualityList = MediaStreams.GetAllVideoQualityLabels().ToList();
+ qualityList.Sort(new QualityComparer());
+ qualityList.Reverse();
+
+ foreach (string i in qualityList)
+ {
+ object tag;
+ if (MediaStreams.Muxed.Any(m => m.VideoQualityLabel == i && m.Resolution.Height <= screenHeight))
+ tag = MediaStreams.Muxed.Find(m => m.VideoQualityLabel == i);
+ else if (MediaStreams.Video.Any(m => m.VideoQualityLabel == i && m.Resolution.Height <= screenHeight && m.Container.GetFileExtension() == "webm"))
+ tag = MediaStreams.Video.Find(m => m.VideoQualityLabel == i && m.Container.GetFileExtension() == "webm");
+ else
+ continue;
+
+ quality.Items.Add(new ComboBoxItem
+ {
+ Content = i,
+ Tag = tag
+ });
+ }
+
+ string s = SettingsStorage.VideoQuality == "remember" ? SettingsStorage.RememberedQuality : SettingsStorage.VideoQuality;
+
+ if (quality.Items.Any(i => (i as ComboBoxItem).Content as string == s))
+ quality.SelectedItem = quality.Items.Find(i => (i as ComboBoxItem).Content as string == s);
+ else
+ quality.SelectedIndex = 0;
+
+
+ if (ClosedCaptions.Count == 0)
+ {
+ captionsMenu.Visibility = Visibility.Collapsed;
+ return;
+ }
+
+ foreach (ClosedCaptionTrackInfo i in ClosedCaptions)
+ captions.Items.Add(new ComboBoxItem
{
Content = string.Format("{0} {1}", CultureInfo.GetCultureInfo(i.Language.Code).DisplayName, i.IsAutoGenerated ? ResourceLoader.GetForCurrentView("VideoPage").GetString("/VideoPage/generatedCaption") : ""),
Tag = i
});
- ClosedCaptionTrackInfo item = list.Find(i => SettingsStorage.RelevanceLanguage.Contains(i.Language.Code)) ?? list.Find(i => "en-US".Contains(i.Language.Code));
+ ClosedCaptionTrackInfo item = ClosedCaptions.Find(i => SettingsStorage.RelevanceLanguage.Contains(i.Language.Code)) ?? ClosedCaptions.Find(i => "en-US".Contains(i.Language.Code));
if (item == null)
- item = list.First();
+ item = ClosedCaptions.First();
- (GetTemplateChild("ccSelector") as ComboBox).SelectedItem = (GetTemplateChild("ccSelector") as ComboBox).Items.Find(i => (i as ComboBoxItem).Tag == item);
+ captions.SelectedItem = captions.Items.Find(i => (i as ComboBoxItem).Tag as ClosedCaptionTrackInfo == item);
+
+ Caption.Player = Player;
}
else
- (GetTemplateChild("cc") as Button).Visibility = Visibility.Collapsed;
- }
-
- public void SetQualities(MediaStreamInfoSet list)
- {
- if (!isReady)
{
- queue.Enqueue(() => SetQualities(list));
- return;
+ captionsMenu.Visibility = Visibility.Collapsed;
+ seek.Visibility = Visibility.Collapsed;
+ live.Visibility = Visibility.Visible;
+ remain.Visibility = Visibility.Collapsed;
+ elapsed.FontSize = 24;
+ Grid.SetRow(elapsed, 0);
+ Grid.SetRowSpan(elapsed, 2);
+ elapsed.HorizontalAlignment = HorizontalAlignment.Right;
+ fwd.Visibility = Visibility.Collapsed;
+ bwd.Visibility = Visibility.Collapsed;
+
+ List list = await ManifestGenerator.ResolveLiveSteream(MediaStreams.HlsLiveStreamUrl);
+
+ foreach (StreamQuality i in list)
+ quality.Items.Add(new ComboBoxItem
+ {
+ Content = i.Resolution,
+ Tag = i
+ });
+
+ string s = SettingsStorage.VideoQuality == "remember" ? SettingsStorage.RememberedQuality : SettingsStorage.VideoQuality;
+
+ if (quality.Items.Any(i => (i as ComboBoxItem).Content as string == s))
+ quality.SelectedItem = quality.Items.Find(i => (i as ComboBoxItem).Content as string == s);
+ else
+ quality.SelectedIndex = 0;
}
-
- MediaStreams = list;
-
- List q = list.GetAllVideoQualities().ToList();
- q.Sort();
- q.Reverse();
- List labels = new List();
- q.ForEach(i => labels.Add(i.GetVideoQualityLabel()));
- (GetTemplateChild("qualitySelector") as ComboBox).ItemsSource = labels;
-
- string s = SettingsStorage.VideoQuality == "remember" ? SettingsStorage.RememberedQuality : SettingsStorage.VideoQuality;
-
- if (labels.Contains(s))
- (GetTemplateChild("qualitySelector") as ComboBox).SelectedItem = s;
- else
- (GetTemplateChild("qualitySelector") as ComboBox).SelectedIndex = 0;
- }
-
- public void SetStream(string url)
- {
- if (!isReady)
- {
- queue.Enqueue(() => SetStream(url));
- return;
- }
-
- (GetTemplateChild("goLive") as Button).Visibility = Visibility.Visible;
- (GetTemplateChild("sliderPan") as Grid).Children.Remove(GetTemplateChild("TimeElapsedElement") as TextBlock);
- (GetTemplateChild("rightStack") as StackPanel).Children.Insert(0, GetTemplateChild("TimeElapsedElement") as TextBlock);
- (GetTemplateChild("TimeElapsedElement") as TextBlock).VerticalAlignment = VerticalAlignment.Center;
- (GetTemplateChild("TimeElapsedElement") as TextBlock).FontSize = 18;
- (GetTemplateChild("TimeElapsedElement") as TextBlock).Margin = new Thickness(10, 0, 10, 0);
- (GetTemplateChild("sliderPan") as Grid).Visibility = Visibility.Collapsed;
- (GetTemplateChild("cc") as Button).Visibility = Visibility.Collapsed;
- (GetTemplateChild("quality") as Button).Visibility = Visibility.Collapsed;
-
- Player.Source = MediaSource.CreateFromUri(url.ToUri());
}
}
}
diff --git a/FoxTube/Controls/Player/VideoPlayer.xaml b/FoxTube/Controls/Player/VideoPlayer.xaml
index d0849ca..008f27b 100644
--- a/FoxTube/Controls/Player/VideoPlayer.xaml
+++ b/FoxTube/Controls/Player/VideoPlayer.xaml
@@ -8,16 +8,17 @@
mc:Ignorable="d"
d:DesignHeight="1080"
d:DesignWidth="1920"
- RequestedTheme="Dark">
+ RequestedTheme="Dark"
+ Visibility="Collapsed">
-
-
+
+
-
-
+
+
diff --git a/FoxTube/Controls/Player/VideoPlayer.xaml.cs b/FoxTube/Controls/Player/VideoPlayer.xaml.cs
index 63c2ea0..6385e29 100644
--- a/FoxTube/Controls/Player/VideoPlayer.xaml.cs
+++ b/FoxTube/Controls/Player/VideoPlayer.xaml.cs
@@ -19,6 +19,8 @@ using Windows.Storage.Pickers;
using Windows.Storage;
using Windows.Media.MediaProperties;
using FoxTube.Controls.Player;
+using System.Diagnostics;
+using Windows.UI.Xaml.Media;
namespace FoxTube
{
@@ -33,15 +35,7 @@ namespace FoxTube
public event ObjectEventHandler MiniMode;
public PlayerControls Controls => videoSource.TransportControls as PlayerControls;
- public MediaPlayer Player { get; } = new MediaPlayer();
- public TimeSpan Position
- {
- get { return videoSource.MediaPlayer.PlaybackSession.Position; }
- set { videoSource.MediaPlayer.PlaybackSession.Position = value; }
- }
-
- //TimeSpan timecodeBackup;
- //bool needUpdateTimecode = false;
+ public MediaElement Player => videoSource;
SystemMediaTransportControls systemControls;
@@ -50,7 +44,7 @@ namespace FoxTube
InitializeComponent();
}
- public async void Initialize(Video meta, string channelAvatar, bool privateMode = false)
+ public void Initialize(Video meta, string channelAvatar, bool privateMode = false)
{
incognito = privateMode;
item = meta;
@@ -61,58 +55,41 @@ namespace FoxTube
};
videoSource.PosterSource = new BitmapImage((meta.Snippet.Thumbnails.Maxres ?? meta.Snippet.Thumbnails.Medium).Url.ToUri());
- Controls.SetMeta(meta.Snippet.Localized.Title, meta.Snippet.ChannelTitle);
if (item.Snippet.LiveBroadcastContent == "none")
{
InitializeContols();
- // TODO: make ads live again
- /*if (Methods.GetDuration(item.ContentDetails.Duration).TotalMinutes > 5)
- Player.PlaybackSession.Add(new Windows.UI.Xaml.Media.TimelineMarker { Time = Methods.GetDuration(item.ContentDetails.Duration) - TimeSpan.FromMinutes(1) });
+ Controls.Load(item);
+
+ if (Methods.GetDuration(item.ContentDetails.Duration).TotalMinutes > 5)
+ videoSource.Markers.Add(new TimelineMarker { Time = Methods.GetDuration(item.ContentDetails.Duration) - TimeSpan.FromMinutes(1) });
if(Methods.GetDuration(item.ContentDetails.Duration).TotalMinutes >= 60)
for (int k = 1; k < Methods.GetDuration(item.ContentDetails.Duration).TotalMinutes / 30; k++)
- videoSource.Markers.Add(new Windows.UI.Xaml.Media.TimelineMarker { Time = TimeSpan.FromMinutes(k * 30) });*/
+ videoSource.Markers.Add(new TimelineMarker { Time = TimeSpan.FromMinutes(k * 30) });
- Controls.SetQualities(await new YoutubeClient().GetVideoMediaStreamInfosAsync(item.Id));
- Controls.SetCaptions(await new YoutubeClient().GetVideoClosedCaptionTrackInfosAsync(item.Id));
+ if (!privateMode)
+ HistorySet.Update(history);
}
else if (item.Snippet.LiveBroadcastContent == "live")
{
InitializeContols();
- Controls.IsSkipBackwardButtonVisible = false;
- Controls.IsSkipForwardButtonVisible = false;
- Controls.LiveRequested += Controls_LiveRequested;
- object i = await new YoutubeClient().GetVideoMediaStreamInfosAsync(item.Id);
- Controls.SetStream((await new YoutubeClient().GetVideoMediaStreamInfosAsync(item.Id)).HlsLiveStreamUrl);
+ Controls.Load(item);
}
else
videoSource.AreTransportControlsEnabled = false;
- if (!privateMode)
- HistorySet.Update(history);
-
Visibility = Visibility.Visible;
}
- private void Controls_LiveRequested(object sender, RoutedEventArgs e)
- {
- Position = Player.PlaybackSession.NaturalDuration;
- }
-
public void InitializeContols()
{
- videoSource.SetMediaPlayer(Player);
- Player.Volume = SettingsStorage.Volume;
-
- Player.CurrentStateChanged += VideoSource_CurrentStateChanged;
- Player.MediaOpened += VideoSource_MediaOpened;
- Player.VolumeChanged += VideoSource_VolumeChanged;
+ videoSource.Volume = SettingsStorage.Volume;
+ videoSource.AutoPlay = SettingsStorage.Autoplay;
Controls.CloseRequested += Controls_CloseRequested;
Controls.NextRequested += (s, e) => NextClicked?.Invoke();
- Controls.QualityChanged += Controls_QualityChanged;
Controls.MiniModeChanged += Controls_MiniModeChanged;
- Controls.Player = Player;
+ Controls.Player = videoSource;
#region System Media Transport Controls
systemControls = SystemMediaTransportControls.GetForCurrentView();
@@ -131,6 +108,25 @@ namespace FoxTube
#endregion
}
+ private async void SystemControls_Engaged(SystemMediaTransportControls sender, SystemMediaTransportControlsButtonPressedEventArgs args)
+ {
+ await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
+ {
+ switch (args.Button)
+ {
+ case SystemMediaTransportControlsButton.Play:
+ videoSource.Play();
+ break;
+ case SystemMediaTransportControlsButton.Pause:
+ videoSource.Pause();
+ break;
+ case SystemMediaTransportControlsButton.Next:
+ NextClicked?.Invoke();
+ break;
+ }
+ });
+ }
+
public void Controls_MiniModeChanged(object sender, bool e)
{
videoSource.IsFullWindow = false;
@@ -143,101 +139,46 @@ namespace FoxTube
Controls.Minimize();
}
- private async void Controls_QualityChanged(object sender, MediaStreamInfo requestedQuality, MediaStreamInfoSet list)
- {
- Player.Pause();
-
- Player.Source = MediaSource.CreateFromUri(await ManifestGenerator.GetManifest(item, requestedQuality as VideoStreamInfo, list));
-
- //await ManifestGenerator.GetManifest(item, requestedQuality as VideoStreamInfo, list);
- /*FileOpenPicker picker = new FileOpenPicker();
- picker.FileTypeFilter.Add(".mpd");*/
-
- //Player.Source = MediaSource.CreateFromUri("https://foxsharp.000webhostapp.com/dash_sample.mpd".ToUri());
-
- /*timecodeBackup = videoSource.Position;
- needUpdateTimecode = true;
-
- if (requestedQuality is MuxedStreamInfo)
- videoSource.Source = requestedQuality.Url.ToUri();
- else
- processor.Start(requestedQuality as VideoStreamInfo, list);*/
- }
-
public void Controls_CloseRequested(object sender, RoutedEventArgs e)
{
- if(systemControls != null)
- systemControls.IsEnabled = false;
-
- if (!incognito)
+ videoSource.Pause();
+ systemControls.IsEnabled = false;
+ videoSource.Source = null;
+
+ if (!incognito && item.Snippet.LiveBroadcastContent == "none")
{
- history.LeftOn = Player.PlaybackSession.Position;
+ history.LeftOn = videoSource.Position;
HistorySet.Update(history);
}
Methods.MainPage.CloseVideo();
}
- private async void SystemControls_Engaged(SystemMediaTransportControls sender, SystemMediaTransportControlsButtonPressedEventArgs args)
- {
- await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
- {
- switch (args.Button)
- {
- case SystemMediaTransportControlsButton.Play:
- Player.Play();
- break;
- case SystemMediaTransportControlsButton.Pause:
- Player.Pause();
- break;
- case SystemMediaTransportControlsButton.Next:
- NextClicked?.Invoke();
- break;
- }
- });
- }
-
public void Pause()
{
- Player.Pause();
+ videoSource.Pause();
}
- private void VideoSource_CurrentStateChanged(MediaPlayer sender, object e)
+ private void VideoSource_CurrentStateChanged(object sender, RoutedEventArgs e)
{
- switch (Player.PlaybackSession.PlaybackState)
- {
- case MediaPlaybackState.Buffering:
- case MediaPlaybackState.None:
- case MediaPlaybackState.Opening:
- case MediaPlaybackState.Paused:
- systemControls.PlaybackStatus = MediaPlaybackStatus.Paused;
- if (!incognito)
- {
- history.LeftOn = Player.PlaybackSession.Position;
- HistorySet.Update(history);
- }
- break;
- case MediaPlaybackState.Playing:
- systemControls.PlaybackStatus = MediaPlaybackStatus.Playing;
- break;
- }
+ systemControls.PlaybackStatus = videoSource.CurrentState == MediaElementState.Playing ? MediaPlaybackStatus.Playing : MediaPlaybackStatus.Paused;
+
+ if(videoSource.CurrentState == MediaElementState.Paused)
+ if (!incognito && item.Snippet.LiveBroadcastContent == "none")
+ {
+ history.LeftOn = videoSource.Position;
+ HistorySet.Update(history);
+ }
}
- private void VideoSource_MediaOpened(MediaPlayer sender, object e)
+ private void VideoSource_VolumeChanged(object sender, RoutedEventArgs e)
{
- /*if (!needUpdateTimecode)
- return;
-
- videoSource.Position = timecodeBackup;
- needUpdateTimecode = false;*/
+ if(videoSource.Volume != 0)
+ SettingsStorage.Volume = videoSource.Volume;
+ Controls.UpdateVolumeIcon();
}
- private void VideoSource_VolumeChanged(MediaPlayer sender, object e)
- {
- SettingsStorage.Volume = Player.Volume;
- }
-
- private void VideoSource_MarkerReached(object sender, Windows.UI.Xaml.Media.TimelineMarkerRoutedEventArgs e)
+ private void VideoSource_MarkerReached(object sender, TimelineMarkerRoutedEventArgs e)
{
Controls.Advert.PushAdvert();
}
diff --git a/FoxTube/Controls/VideoCard.xaml.cs b/FoxTube/Controls/VideoCard.xaml.cs
index 586aee2..faffa15 100644
--- a/FoxTube/Controls/VideoCard.xaml.cs
+++ b/FoxTube/Controls/VideoCard.xaml.cs
@@ -35,6 +35,7 @@ namespace FoxTube.Controls
Initialize(id, playlist);
}
+
public VideoCard(Video meta, string playlist = null)
{
InitializeComponent();
@@ -108,6 +109,7 @@ namespace FoxTube.Controls
watched.Visibility = Visibility.Visible;
if (HistorySet.Items.Exists(i => i.Id == item.Id))
{
+ history = HistorySet.Items.Find(i => i.Id == item.Id);
watched.Visibility = Visibility.Visible;
leftOn.Value = 100 * HistorySet.Items.Find(i => i.Id == item.Id).LeftOn.TotalSeconds / Methods.GetDuration(item.ContentDetails.Duration).TotalSeconds;
}
diff --git a/FoxTube/FoxTube.csproj b/FoxTube/FoxTube.csproj
index 154879a..cb67b27 100644
--- a/FoxTube/FoxTube.csproj
+++ b/FoxTube/FoxTube.csproj
@@ -430,7 +430,7 @@
10.1811.22001
- 1.14.0
+ 2.0.0
6.2.8
@@ -451,7 +451,7 @@
4.3.2
- 4.7.0-beta
+ 4.7.0
diff --git a/FoxTube/Pages/VideoPage.xaml.cs b/FoxTube/Pages/VideoPage.xaml.cs
index 0d2ae71..bfc9be7 100644
--- a/FoxTube/Pages/VideoPage.xaml.cs
+++ b/FoxTube/Pages/VideoPage.xaml.cs
@@ -444,8 +444,8 @@ namespace FoxTube.Pages
private async void openBrowser_Click(object sender, RoutedEventArgs e)
{
player.Pause();
- string timecode = player.Position.TotalSeconds > 10 ?
- "&t=" + (int)player.Position.TotalSeconds + "s" : string.Empty;
+ string timecode = player.Player.Position.TotalSeconds > 10 ?
+ "&t=" + (int)player.Player.Position.TotalSeconds + "s" : string.Empty;
await Launcher.LaunchUriAsync($"https://www.youtube.com/watch?v={videoId}{timecode}".ToUri());
}
@@ -778,7 +778,7 @@ namespace FoxTube.Pages
private void Left_Click(object sender, RoutedEventArgs e)
{
- Player.Position = history.LeftOn;
+ Player.Player.Position = history.LeftOn;
}
}
}
diff --git a/FoxTube/Themes/Generic.xaml b/FoxTube/Themes/Generic.xaml
index 32bdbfc..ce5a0e6 100644
--- a/FoxTube/Themes/Generic.xaml
+++ b/FoxTube/Themes/Generic.xaml
@@ -6,9 +6,7 @@
xmlns:adverts="using:FoxTube.Controls.Adverts">
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -156,29 +73,17 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -189,156 +94,43 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
+ Height="15" Width="15" Grid.Column="1"
+ AutomationProperties.AccessibilityView="Raw">
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -348,7 +140,6 @@
-
@@ -356,66 +147,34 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
+
-
-
- Visible
-
-
+
-
-
- Visible
-
-
+
-->
-
+
-
-
-
-
-
-
-
-
-
-
-
-
@@ -467,7 +208,6 @@
-
@@ -481,161 +221,132 @@
+
+
+
-
-
-
-
+
-
+
-
-
-
-
-
+
+
+
+
-
+
-
+
-
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
-
-
-
-
-
+
-
+
+
+
+
-
+
-
+
-
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
-
-
-
+
-
-
+
+
-
-
-
+
-
+
-
-
-
-
+
+
+