Archived
1
0

Merged PR 25: [0.6] [Release candidate] Submission #5

#Final minor version. First 1.0 candidate

- Version number updated
- [Experemental] Fixed authorization fail

Related Work Items: #281
- Fixed ads appearance
Fixed ads watermarks on video when it was opened through notification
Fixed videos loading
- Updated changelog
Added "Sponsored content" watermark on CardAdverts
- Cursor now hides on playback
Special chars in toasts fixed
- - 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

Related Work Items: #140, #252
- Updated localization
- - Cursor hiding on playback removed
- Ad free version price is displayed now
- Video player muxed streams playback development
- [In progress] - Rebuilding video player mechanics to work with DASH manifest
- Fixed crashes on opening links which don't contain http(s) prefix
- Fixed backward navigation with minimized video
- - Updated NuGet packs
- Added quality switcher to livestreams
- Fixed SettingsStorage cast
- Completed ManifestGenerator (but still doesn't work)
- Refactored and redesigned player
- Created test project
- ManifestGenerator upate
- - Fixed manifest generator
- Added "Auto" quality
- - Updated patchnote and version
- Updated user's avatar design
- Updated screenshots
This commit is contained in:
Michael Gordeev
2019-05-18 16:41:47 +00:00
80 changed files with 2082 additions and 988 deletions
+1 -1
View File
@@ -123,7 +123,7 @@ namespace FoxTube.Background
{ {
public static string ConvertEscapeSymbols(this string str) public static string ConvertEscapeSymbols(this string str)
{ {
return str.Replace("&quot;", "\"").Replace("&apos;", "'").Replace("&lt;", "<").Replace("&gt;", ">").Replace("&amp;", "&"); return str.Replace("&quot;", "\"").Replace("&apos;", "'").Replace("&lt;", "<").Replace("&gt;", ">").Replace("&amp;", "&").Replace("&#34;", "\"").Replace("&#39;", "'").Replace("&#60;", "<").Replace("&#62;", ">").Replace("&#38;", "&");
} }
} }
} }
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

+172
View File
@@ -0,0 +1,172 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
<ProjectGuid>{3D864717-2D87-4E54-BFC0-755FC2FCA2A7}</ProjectGuid>
<OutputType>AppContainerExe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>FoxTube.Tests</RootNamespace>
<AssemblyName>FoxTube.Tests</AssemblyName>
<DefaultLanguage>en-US</DefaultLanguage>
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
<TargetPlatformVersion>10.0.17134.0</TargetPlatformVersion>
<TargetPlatformMinVersion>10.0.17134.0</TargetPlatformMinVersion>
<MinimumVisualStudioVersion>14</MinimumVisualStudioVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<PackageCertificateKeyFile>FoxTube.Tests_TemporaryKey.pfx</PackageCertificateKeyFile>
<UnitTestPlatformVersion Condition="'$(UnitTestPlatformVersion)' == ''">$(VisualStudioVersion)</UnitTestPlatformVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x86\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<NoWarn>;2008</NoWarn>
<DebugType>full</DebugType>
<PlatformTarget>x86</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<OutputPath>bin\x86\Release\</OutputPath>
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<Optimize>true</Optimize>
<NoWarn>;2008</NoWarn>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|ARM'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\ARM\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<NoWarn>;2008</NoWarn>
<DebugType>full</DebugType>
<PlatformTarget>ARM</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|ARM'">
<OutputPath>bin\ARM\Release\</OutputPath>
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<Optimize>true</Optimize>
<NoWarn>;2008</NoWarn>
<DebugType>pdbonly</DebugType>
<PlatformTarget>ARM</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|ARM64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\ARM64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<NoWarn>;2008</NoWarn>
<DebugType>full</DebugType>
<PlatformTarget>ARM64</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|ARM64'">
<OutputPath>bin\ARM64\Release\</OutputPath>
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<Optimize>true</Optimize>
<NoWarn>;2008</NoWarn>
<DebugType>pdbonly</DebugType>
<PlatformTarget>ARM64</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<NoWarn>;2008</NoWarn>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<Optimize>true</Optimize>
<NoWarn>;2008</NoWarn>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
</PropertyGroup>
<PropertyGroup>
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
</PropertyGroup>
<ItemGroup>
<SDKReference Include="TestPlatform.Universal, Version=$(UnitTestPlatformVersion)" />
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="UnitTestApp.xaml.cs">
<DependentUpon>UnitTestApp.xaml</DependentUpon>
</Compile>
<Compile Include="UnitTest.cs" />
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="UnitTestApp.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
</ItemGroup>
<ItemGroup>
<AppxManifest Include="Package.appxmanifest">
<SubType>Designer</SubType>
</AppxManifest>
<None Include="FoxTube.Tests_TemporaryKey.pfx" />
</ItemGroup>
<ItemGroup>
<Content Include="Properties\UnitTestApp.rd.xml" />
<Content Include="Assets\LockScreenLogo.scale-200.png" />
<Content Include="Assets\SplashScreen.scale-200.png" />
<Content Include="Assets\Square150x150Logo.scale-200.png" />
<Content Include="Assets\Square44x44Logo.scale-200.png" />
<Content Include="Assets\Square44x44Logo.targetsize-24_altform-unplated.png" />
<Content Include="Assets\StoreLogo.png" />
<Content Include="Assets\Wide310x150Logo.scale-200.png" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
<Version>6.2.8</Version>
</PackageReference>
<PackageReference Include="MSTest.TestAdapter">
<Version>1.4.0</Version>
</PackageReference>
<PackageReference Include="MSTest.TestFramework">
<Version>1.4.0</Version>
</PackageReference>
</ItemGroup>
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '14.0' ">
<VisualStudioVersion>14.0</VisualStudioVersion>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
+46
View File
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<Package
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
IgnorableNamespaces="uap mp">
<Identity Name="d50f8eba-0ea7-439d-850c-b1882d571731"
Publisher="CN=XFox"
Version="1.0.0.0" />
<mp:PhoneIdentity PhoneProductId="d50f8eba-0ea7-439d-850c-b1882d571731" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
<Properties>
<DisplayName>FoxTube.Tests</DisplayName>
<PublisherDisplayName>XFox</PublisherDisplayName>
<Logo>Assets\StoreLogo.png</Logo>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.0.0" MaxVersionTested="10.0.0.0" />
</Dependencies>
<Resources>
<Resource Language="x-generate" />
</Resources>
<Applications>
<Application Id="vstest.executionengine.universal.App"
Executable="$targetnametoken$.exe"
EntryPoint="FoxTube.Tests.App">
<uap:VisualElements
DisplayName="FoxTube.Tests"
Square150x150Logo="Assets\Square150x150Logo.png"
Square44x44Logo="Assets\Square44x44Logo.png"
Description="FoxTube.Tests"
BackgroundColor="transparent">
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png"/>
<uap:SplashScreen Image="Assets\SplashScreen.png" />
</uap:VisualElements>
</Application>
</Applications>
<Capabilities>
<Capability Name="internetClientServer" />
<Capability Name="privateNetworkClientServer" />
</Capabilities>
</Package>
+18
View File
@@ -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)]
@@ -0,0 +1,29 @@
<!--
This file contains Runtime Directives used by .NET Native. The defaults here are suitable for most
developers. However, you can modify these parameters to modify the behavior of the .NET Native
optimizer.
Runtime Directives are documented at https://go.microsoft.com/fwlink/?LinkID=391919
To fully enable reflection for App1.MyClass and all of its public/private members
<Type Name="App1.MyClass" Dynamic="Required All"/>
To enable dynamic creation of the specific instantiation of AppClass<T> over System.Int32
<TypeInstantiation Name="App1.AppClass" Arguments="System.Int32" Activate="Required Public" />
Using the Namespace directive to apply reflection policy to all the types in a particular namespace
<Namespace Name="DataClasses.ViewModels" Serialize="All" />
-->
<Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata">
<Application>
<!--
An Assembly element with Name="*Application*" applies to all assemblies in
the application package. The asterisks are not wildcards.
-->
<Assembly Name="*Application*" Dynamic="Required All" />
<!-- Add your application specific runtime directives here. -->
</Application>
</Directives>
+15
View File
@@ -0,0 +1,15 @@
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace FoxTube.Tests
{
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
}
}
}
+7
View File
@@ -0,0 +1,7 @@
<Application
x:Class="FoxTube.Tests.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:FoxTube.Tests">
</Application>
+102
View File
@@ -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
{
/// <summary>
/// Provides application-specific behavior to supplement the default Application class.
/// </summary>
sealed partial class App : Application
{
/// <summary>
/// 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().
/// </summary>
public App()
{
this.InitializeComponent();
this.Suspending += OnSuspending;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="e">Details about the launch request and process.</param>
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);
}
/// <summary>
/// Invoked when Navigation to a certain page fails
/// </summary>
/// <param name="sender">The Frame which failed navigation</param>
/// <param name="e">Details about the navigation failure</param>
void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
{
throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="sender">The source of the suspend request.</param>
/// <param name="e">Details about the suspend request.</param>
private void OnSuspending(object sender, SuspendingEventArgs e)
{
var deferral = e.SuspendingOperation.GetDeferral();
//TODO: Save application state and stop any background activity
deferral.Complete();
}
}
}
+38 -2
View File
@@ -1,20 +1,24 @@
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15 # Visual Studio Version 16
VisualStudioVersion = 15.0.27428.1 VisualStudioVersion = 16.0.28803.352
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FoxTube", "FoxTube\FoxTube.csproj", "{2597B816-7316-4D20-BA6C-D78001E89C1A}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FoxTube", "FoxTube\FoxTube.csproj", "{2597B816-7316-4D20-BA6C-D78001E89C1A}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FoxTube.Background", "FoxTube.Background\FoxTube.Background.csproj", "{FC9128D7-E3AA-48ED-8641-629794B88B28}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FoxTube.Background", "FoxTube.Background\FoxTube.Background.csproj", "{FC9128D7-E3AA-48ED-8641-629794B88B28}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FoxTube.Tests", "FoxTube.Tests\FoxTube.Tests.csproj", "{3D864717-2D87-4E54-BFC0-755FC2FCA2A7}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
Debug|ARM = Debug|ARM Debug|ARM = Debug|ARM
Debug|ARM64 = Debug|ARM64
Debug|x64 = Debug|x64 Debug|x64 = Debug|x64
Debug|x86 = Debug|x86 Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU Release|Any CPU = Release|Any CPU
Release|ARM = Release|ARM Release|ARM = Release|ARM
Release|ARM64 = Release|ARM64
Release|x64 = Release|x64 Release|x64 = Release|x64
Release|x86 = Release|x86 Release|x86 = Release|x86
EndGlobalSection EndGlobalSection
@@ -23,6 +27,7 @@ Global
{2597B816-7316-4D20-BA6C-D78001E89C1A}.Debug|ARM.ActiveCfg = Debug|ARM {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.Build.0 = Debug|ARM
{2597B816-7316-4D20-BA6C-D78001E89C1A}.Debug|ARM.Deploy.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.ActiveCfg = Debug|x64
{2597B816-7316-4D20-BA6C-D78001E89C1A}.Debug|x64.Build.0 = Debug|x64 {2597B816-7316-4D20-BA6C-D78001E89C1A}.Debug|x64.Build.0 = Debug|x64
{2597B816-7316-4D20-BA6C-D78001E89C1A}.Debug|x64.Deploy.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.ActiveCfg = Release|ARM
{2597B816-7316-4D20-BA6C-D78001E89C1A}.Release|ARM.Build.0 = 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|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.ActiveCfg = Release|x64
{2597B816-7316-4D20-BA6C-D78001E89C1A}.Release|x64.Build.0 = Release|x64 {2597B816-7316-4D20-BA6C-D78001E89C1A}.Release|x64.Build.0 = Release|x64
{2597B816-7316-4D20-BA6C-D78001E89C1A}.Release|x64.Deploy.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|Any CPU.Build.0 = Debug|Any CPU
{FC9128D7-E3AA-48ED-8641-629794B88B28}.Debug|ARM.ActiveCfg = Debug|ARM {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|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.ActiveCfg = Debug|x64
{FC9128D7-E3AA-48ED-8641-629794B88B28}.Debug|x64.Build.0 = Debug|x64 {FC9128D7-E3AA-48ED-8641-629794B88B28}.Debug|x64.Build.0 = Debug|x64
{FC9128D7-E3AA-48ED-8641-629794B88B28}.Debug|x86.ActiveCfg = Debug|x86 {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|Any CPU.Build.0 = Release|Any CPU
{FC9128D7-E3AA-48ED-8641-629794B88B28}.Release|ARM.ActiveCfg = Release|ARM {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|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.ActiveCfg = Release|x64
{FC9128D7-E3AA-48ED-8641-629794B88B28}.Release|x64.Build.0 = 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.ActiveCfg = Release|x86
{FC9128D7-E3AA-48ED-8641-629794B88B28}.Release|x86.Build.0 = 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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
+1
View File
@@ -267,6 +267,7 @@ namespace FoxTube
SettingsStorage.SaveData(); SettingsStorage.SaveData();
DownloadAgent.QuitPrompt(); DownloadAgent.QuitPrompt();
Controls.Player.ManifestGenerator.ClearRoaming();
deferral.Complete(); deferral.Complete();
Analytics.TrackEvent("Session terminated"); Analytics.TrackEvent("Session terminated");
} }
+56
View File
@@ -1,5 +1,61 @@
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="utf-8" ?>
<items> <items>
<item time="2019-04-28" version="0.6">
<content>
<en-US>##[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
</en-US>
<ru-RU>##[Последняя предварительная версия]
### Что нового:
- Исправлена проблема получения истории, "Посмотреть позже" и рекомендаций
- Исправлен внешний вид рекламы
- Исправлено появление водяных занков рекламы на видео при открытии через уведомления
- Оптимизирован и улучшен просмотр видео
- Исправлено появление особых символов в уведомлениях
- Редизайн страницы истории
- Добавлено управление историей просмотра приложения (не влияет на историю просмотров на сайте)
- Расширенная информация о просмотренном видео (прогресс просмотра)
- Функция продолжения просмотра
- Плейлист "Посмотреть позже" теперь ведет себя как обычный плейлист
- Если видео длится более 1 часа, рекламный баннер будет появляться каждые 30 минут
- Добавлен режим инкогнито (доступен в контекстном меню видео карточки)
- Подсказки при поиске работают плавнее
- Теперь на кнопке отключения рекламы отображается текущая цена
- Исправлены вылеты при попытке открыть ссылку не содержащую http(s) префикс
- Исправлена обратная навигация при уменьшенном видео
- Редизайн плеера
- Добавлено меню выбора качества для прямых эфиров
- Добавлено опция "Авто" в меню выбора качеста видео
- Обновлен дизайн аватара пользователя в верхнем правом углу
####[NB]
Версия 0.6 станет последней пред релизной версией. Это значит, что новые функции не будут добовляться до полного релиза приложения. Все последующие обновления будут содержать лишь исправления
</ru-RU>
</content>
</item>
<item time="2019-04-06" version="0.5"> <item time="2019-04-06" version="0.5">
<content> <content>
<en-US>### What's new: <en-US>### What's new:
+338
View File
@@ -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<Uri> 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<StreamInfo> 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 = (?<Json>\{[^\{\}]*(((?<Open>\{)[^\{\}]*)+((?<Close-Open>\})[^\{\}]*)+)*(?(Open)(?!))\})")
.Groups["Json"].Value;
JToken playerConfigJson = JToken.Parse(playerConfigRaw);
var playerResponseRaw = playerConfigJson.SelectToken("args.player_response").Value<string>();
JToken playerResponseJson = JToken.Parse(playerResponseRaw);
string errorReason = playerResponseJson.SelectToken("playabilityStatus.reason")?.Value<string>();
if (!string.IsNullOrWhiteSpace(errorReason))
throw new InvalidDataException($"Video [{info.Id}] is unplayable. Reason: {errorReason}");
List<Dictionary<string, string>> adaptiveStreamInfosUrl = playerConfigJson.SelectToken("args.adaptive_fmts")?.Value<string>().Split(',').Select(SplitQuery).ToList();
List<Dictionary<string, string>> video =
requestedQuality == null ?
adaptiveStreamInfosUrl.FindAll(i => i.ContainsKey("quality_label")) :
adaptiveStreamInfosUrl.FindAll(i => i.ContainsValue(requestedQuality.VideoQualityLabel));
List<Dictionary<string, string>> 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<string, string> SplitQuery(string query)
{
Dictionary<string, string> dic = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
string[] paramsEncoded = query.TrimStart('?').Split("&");
foreach (string paramEncoded in paramsEncoded)
{
string param = WebUtility.UrlDecode(paramEncoded);
// Look for the equals sign
int equalsPos = param.IndexOf('=');
if (equalsPos <= 0)
continue;
// Get the key and value
string key = param.Substring(0, equalsPos);
string value = equalsPos < param.Length
? param.Substring(equalsPos + 1)
: string.Empty;
// Add to dictionary
dic[key] = value;
}
return dic;
}
private static 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<List<StreamQuality>> ResolveLiveSteream(string url)
{
List<StreamQuality> list = new List<StreamQuality>();
string playlistRaw = await new HttpClient().GetStringAsync(url);
List<string> streamsRaw = playlistRaw.Split("#EXT-X-STREAM-INF:").ToList();
streamsRaw.RemoveAt(0);
List<Dictionary<string, string>> streams = new List<Dictionary<string, string>>();
foreach (string i in streamsRaw)
{
Dictionary<string, string> item = new Dictionary<string, string>();
string[] par = i.Split('\n');
item.Add("URL", par[1]);
par = par[0].Split(',');
foreach (string k in par)
{
string[] pair = k.Split('=');
if (pair.Length < 2)
continue;
item[pair[0]] = pair[1];
}
streams.Add(item);
}
foreach (var i in streams)
{
StreamQuality item = new StreamQuality();
item.Resolution = $"{i["RESOLUTION"].Split('x')[1]}p";
item.Url = i["URL"].ToUri();
list.Add(item);
}
list.Add(new StreamQuality
{
Resolution = ResourceLoader.GetForCurrentView("VideoPage").GetString("/VideoPage/auto"),
Url = url.ToUri()
});
list.Reverse();
return list;
}
public static async void ClearRoaming()
{
IReadOnlyList<StorageFile> items = await roaming.GetFilesAsync();
foreach (StorageFile f in items)
await f.DeleteAsync(StorageDeleteOption.PermanentDelete);
}
}
public class StreamInfo
{
public class VideoInfo
{
public string IndexRange { get; set; }
public string InitRange => $"0-{int.Parse(IndexRange.Split('-')[0]) - 1}";
public 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<VideoInfo> Video { get; } = new List<VideoInfo>();
public List<AudioInfo> Audio { get; } = new List<AudioInfo>();
}
public class StreamQuality
{
public Uri Url { get; set; }
public string Resolution { get; set; }
}
}
+50 -1
View File
@@ -25,6 +25,51 @@ namespace FoxTube
{ {
object Parameter { get; set; } 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<HistoryItem> Items { get; set; } = new List<HistoryItem>();
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<List<HistoryItem>>(ApplicationData.Current.RoamingSettings.Values[$"history-{SecretsVault.AccountId}"] as string);
}
}
public static class Methods public static class Methods
{ {
private static ResourceLoader resources = ResourceLoader.GetForCurrentView("Methods"); private static ResourceLoader resources = ResourceLoader.GetForCurrentView("Methods");
@@ -170,8 +215,12 @@ namespace FoxTube
{ {
if (link.IsMatch(item)) if (link.IsMatch(item))
{ {
string str = item;
if (!str.Contains("http"))
str = str.Insert(0, "http://");
Hyperlink hl = new Hyperlink(); Hyperlink hl = new Hyperlink();
hl.Click += (s, arg) => ProcessLink(item); hl.Click += (s, arg) => ProcessLink(str);
hl.Inlines.Add(new Run { Text = item }); hl.Inlines.Add(new Run { Text = item });
block.Inlines.Add(hl); block.Inlines.Add(hl);
} }
+3 -1
View File
@@ -137,6 +137,8 @@ namespace FoxTube
}, },
"user", "user",
CancellationToken.None); CancellationToken.None);
await Credential.RefreshTokenAsync(CancellationToken.None);
} }
catch (AuthenticateException e) catch (AuthenticateException e)
{ {
@@ -255,7 +257,7 @@ namespace FoxTube
if (!requset.Products["9NP1QK556625"].IsInUserCollection) if (!requset.Products["9NP1QK556625"].IsInUserCollection)
{ {
AdsDisabled = false; AdsDisabled = false;
Purchased?.Invoke(args:false); Purchased?.Invoke(null, false, requset.Products["9NP1QK556625"].Price.FormattedPrice);
} }
} }
catch { } catch { }
+1 -1
View File
@@ -180,7 +180,7 @@ namespace FoxTube
{ {
if (storage.Values["mature"] == null) if (storage.Values["mature"] == null)
{ {
storage.Values["mature"] = MatureState.Blocked; storage.Values["mature"] = (int)MatureState.Blocked;
return MatureState.Blocked; return MatureState.Blocked;
} }
else return (MatureState)storage.Values["mature"]; else return (MatureState)storage.Values["mature"];
+5 -1
View File
@@ -29,7 +29,7 @@
</UserControl.Resources> </UserControl.Resources>
<Button Padding="0" Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}"> <Button Padding="0" Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}">
<Grid> <Grid Name="grid">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition/> <RowDefinition/>
<RowDefinition Height="20"/> <RowDefinition Height="20"/>
@@ -43,6 +43,10 @@
<TextBlock Name="info" Text="CALL TO ACTION TEXT" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="Gray" FontSize="12"/> <TextBlock Name="info" Text="CALL TO ACTION TEXT" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="Gray" FontSize="12"/>
</StackPanel> </StackPanel>
<StackPanel Margin="5" Background="Orange" VerticalAlignment="Top" CornerRadius="5" Opacity=".75" BorderThickness="1" HorizontalAlignment="Left" Padding="5,2,5,3">
<TextBlock Text="Sponsored content" x:Uid="/Cards/sponsored" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="Black" FontSize="12"/>
</StackPanel>
<Grid Grid.Row="1" Name="contentGrid"> <Grid Grid.Row="1" Name="contentGrid">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="60"/> <ColumnDefinition Width="60"/>
+1 -1
View File
@@ -37,7 +37,7 @@ namespace FoxTube.Controls.Adverts
{ {
advert = e.NativeAd; advert = e.NativeAd;
Initialize(); Initialize();
e.NativeAd.RegisterAdContainer(this); e.NativeAd.RegisterAdContainer(grid);
} }
public void Initialize() public void Initialize()
+1 -1
View File
@@ -8,7 +8,7 @@
d:DesignWidth="400" d:DesignWidth="400"
Visibility="Collapsed"> Visibility="Collapsed">
<Grid Margin="2"> <Grid Margin="2" Name="grid" Background="#02000000">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="60"/> <ColumnDefinition Width="60"/>
<ColumnDefinition/> <ColumnDefinition/>
@@ -31,9 +31,9 @@ namespace FoxTube.Controls.Adverts
advert = e.NativeAd; advert = e.NativeAd;
Initialize(); Initialize();
if(cta.Visibility == Visibility.Collapsed) if(cta.Visibility == Visibility.Collapsed)
e.NativeAd.RegisterAdContainer(this); e.NativeAd.RegisterAdContainer(grid);
else else
e.NativeAd.RegisterAdContainer(this, new List<FrameworkElement> { cta }); e.NativeAd.RegisterAdContainer(grid, new List<FrameworkElement> { cta });
} }
private void Initialize() private void Initialize()
+1 -1
View File
@@ -73,7 +73,7 @@
<ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Grid Name="grid" HorizontalAlignment="Stretch"> <Grid Name="grid" HorizontalAlignment="Stretch" Background="#02000000">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/>
<ColumnDefinition/> <ColumnDefinition/>
+10 -8
View File
@@ -205,27 +205,29 @@ namespace FoxTube.Controls
grid.Children.Add(nameStack); grid.Children.Add(nameStack);
grid.Children.Add(contentStack); grid.Children.Add(contentStack);
ListViewItem item = new ListViewItem Grid mainGrid = new Grid();
{ mainGrid.Children.Add(new Border
Content = new Border
{ {
BorderBrush = new SolidColorBrush(Colors.Red), BorderBrush = new SolidColorBrush(Colors.Red),
BorderThickness = new Thickness(2), BorderThickness = new Thickness(2),
CornerRadius = new CornerRadius(5), CornerRadius = new CornerRadius(5),
HorizontalAlignment = HorizontalAlignment.Stretch, HorizontalAlignment = HorizontalAlignment.Stretch,
Margin = new Thickness(0, 2, 0, 2), Margin = new Thickness(0, 27, 0, 2),
Background = new SolidColorBrush(Colors.Red) { Opacity = .2 }, Background = new SolidColorBrush(Colors.Red) { Opacity = .2 },
Child = grid Child = grid
}, });
Padding = new Thickness(0,25,0,0)
ListViewItem item = new ListViewItem
{
Content = mainGrid
}; };
list.Items.Insert(0, item); list.Items.Insert(0, item);
if (contentStack.Children.Last() is HyperlinkButton) if (contentStack.Children.Last() is HyperlinkButton)
advert.RegisterAdContainer(item, new List<FrameworkElement> { contentStack.Children.Last() as HyperlinkButton }); advert.RegisterAdContainer(mainGrid, new List<FrameworkElement> { contentStack.Children.Last() as HyperlinkButton });
else else
advert.RegisterAdContainer(item); advert.RegisterAdContainer(mainGrid);
} }
public async void Update(object sender, object e) public async void Update(object sender, object e)
+3 -1
View File
@@ -52,6 +52,8 @@ namespace FoxTube.Controls
else else
rating.Text = comment.Snippet.TopLevelComment.Snippet.LikeCount.HasValue ? comment.Snippet.TopLevelComment.Snippet.LikeCount.ToString() : ""; rating.Text = comment.Snippet.TopLevelComment.Snippet.LikeCount.HasValue ? comment.Snippet.TopLevelComment.Snippet.LikeCount.ToString() : "";
if(item.Snippet.AuthorChannelId != null)
{
if (item.Snippet.AuthorChannelId.ToString().Split('"')[3] == SecretsVault.AccountId) if (item.Snippet.AuthorChannelId.ToString().Split('"')[3] == SecretsVault.AccountId)
{ {
(specialAuthor.Child as TextBlock).Text = comment.Snippet.TopLevelComment.Snippet.AuthorDisplayName; (specialAuthor.Child as TextBlock).Text = comment.Snippet.TopLevelComment.Snippet.AuthorDisplayName;
@@ -65,7 +67,7 @@ namespace FoxTube.Controls
specialAuthor.Visibility = Visibility.Visible; specialAuthor.Visibility = Visibility.Visible;
author.Visibility = Visibility.Collapsed; 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") : ""); 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") : "");
+16 -1
View File
@@ -1,4 +1,5 @@
using System; using System;
using Windows.Media.Playback;
using Windows.UI.Xaml; using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls;
using YoutubeExplode.Models.ClosedCaptions; using YoutubeExplode.Models.ClosedCaptions;
@@ -16,6 +17,8 @@ namespace FoxTube.Controls
set => text.FontSize = value; set => text.FontSize = value;
} }
public bool IsActive => track != null;
public MediaElement Player { get; set; } public MediaElement Player { get; set; }
DispatcherTimer timer = new DispatcherTimer() { Interval = TimeSpan.FromMilliseconds(10) }; DispatcherTimer timer = new DispatcherTimer() { Interval = TimeSpan.FromMilliseconds(10) };
@@ -50,10 +53,22 @@ namespace FoxTube.Controls
public void Close() public void Close()
{ {
timer.Stop();
track = null; track = null;
currentCaption = null; currentCaption = null;
Visibility = Visibility.Collapsed; 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;
} }
} }
} }
+393 -224
View File
@@ -1,36 +1,111 @@
using FoxTube.Controls; using FoxTube.Controls;
using FoxTube.Controls.Adverts; using FoxTube.Controls.Adverts;
using FoxTube.Controls.Player;
using Google.Apis.YouTube.v3.Data;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using Windows.ApplicationModel.Resources; using Windows.ApplicationModel.Resources;
using Windows.Graphics.Display;
using Windows.Media.Core;
using Windows.UI.Xaml; using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives; using Windows.UI.Xaml.Controls.Primitives;
using YoutubeExplode;
using YoutubeExplode.Models.ClosedCaptions; using YoutubeExplode.Models.ClosedCaptions;
using YoutubeExplode.Models.MediaStreams; using YoutubeExplode.Models.MediaStreams;
namespace FoxTube namespace FoxTube
{ {
public delegate void QualityChangedEventHandler(object sender, MediaStreamInfo requestedQuality, MediaStreamInfoSet list);
public delegate void MinimodeChangedEventHandler(object sender, bool isOn); public delegate void MinimodeChangedEventHandler(object sender, bool isOn);
public enum PlayerDisplayState { Normal, Minimized, Compact }
public class QualityComparer : IComparer<string>
{
public int Compare(string x, string y)
{
string[] xArr = x.Split('p');
string[] yArr = y.Split('p');
int qualityA = int.Parse(xArr[0]);
int qualityB = int.Parse(yArr[0]);
int framerateA = 30;
int framerateB = 30;
if (!string.IsNullOrWhiteSpace(xArr[1]))
framerateA = int.Parse(xArr[1]);
if (!string.IsNullOrWhiteSpace(yArr[1]))
framerateB = int.Parse(yArr[1]);
if (qualityA > qualityB)
return 1;
else if (qualityA < qualityB)
return -1;
else
return framerateA - framerateB > 0 ? 1 : -1;
}
}
/// <summary> /// <summary>
/// Custom controls for media player. MARKUP IS IN **Themes/Generic.xaml**!!! /// Custom controls for media player. MARKUP IS IN **Themes/Generic.xaml**!!!
/// </summary> /// </summary>
public sealed class PlayerControls : MediaTransportControls public sealed class PlayerControls : MediaTransportControls
{ {
public event RoutedEventHandler CloseRequested; public event RoutedEventHandler CloseRequested;
public event RoutedEventHandler LiveRequested;
public event MinimodeChangedEventHandler MiniModeChanged; public event MinimodeChangedEventHandler MiniModeChanged;
public event RoutedEventHandler NextRequested; 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 MediaElement 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 PlayerAdvert Advert;
public LiveCaptions Caption;
TimeSpan timecodeBackup;
bool needUpdateTimecode = false;
public Video Meta { get; set; }
public IReadOnlyList<ClosedCaptionTrackInfo> ClosedCaptions { get; set; } public IReadOnlyList<ClosedCaptionTrackInfo> ClosedCaptions { get; set; }
public MediaStreamInfoSet MediaStreams { get; set; } public MediaStreamInfoSet MediaStreams { get; set; }
@@ -45,31 +120,22 @@ namespace FoxTube
protected override void OnApplyTemplate() protected override void OnApplyTemplate()
{ {
AssignControls();
isReady = true; 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; next.Click += Next_Click;
(GetTemplateChild("compactClose") as Button).Click += Close_Click; volume.ValueChanged += Volume_ValueChanged;
live.Click += Live_Click;
(GetTemplateChild("minimize") as Button).Click += Minimize_Click; captionsSwitch.Toggled += CaptionsSwitch_Toggled;
(GetTemplateChild("maximize") as Button).Click += Minimize_Click; captions.SelectionChanged += Captions_SelectionChanged;
quality.SelectionChanged += Quality_SelectionChanged;
(GetTemplateChild("CompactOverlayButton") as Button).Click += CompactOverlay_Click; seek.ValueChanged += Seek_ValueChanged;
(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);
if (queue.Count > 0) if (queue.Count > 0)
foreach (Action i in queue) foreach (Action i in queue)
@@ -78,96 +144,135 @@ namespace FoxTube
base.OnApplyTemplate(); 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;
MiniModeChanged.Invoke(this, true); miniview = GetTemplateChild("CompactOverlayButton") as Button;
SetMinimized(); 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 || info == null)
Player.SetPlaybackSource(MediaSource.CreateFromUri(await ManifestGenerator.GetManifest(Meta, info as VideoStreamInfo, MediaStreams)));
else if (info is StreamQuality)
Player.SetPlaybackSource(MediaSource.CreateFromUri((info as StreamQuality).Url));
}
private void Captions_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if(Caption.IsActive)
{
Caption.Close();
Caption.Initialize((captions.SelectedItem as ComboBoxItem).Tag as ClosedCaptionTrackInfo);
}
}
private void CaptionsSwitch_Toggled(object sender, RoutedEventArgs e)
{
if(captionsSwitch.IsOn)
Caption.Initialize((captions.SelectedItem as ComboBoxItem).Tag as ClosedCaptionTrackInfo);
else else
{ Caption.Close();
MiniModeChanged.Invoke(this, false);
SetNormal();
}
} }
public void Minimize() private void Live_Click(object sender, RoutedEventArgs e)
{ {
Minimize_Click(GetTemplateChild("minimize"), null); Player.Position = Player.NaturalDuration.TimeSpan;
} }
private void ProgressSlider_ValueChanged(object sender, RangeBaseValueChangedEventArgs e) private void Next_Click(object sender, RoutedEventArgs e)
{ {
(GetTemplateChild("compactSeek") as ProgressBar).Value = e.NewValue; NextRequested.Invoke(sender, e);
} }
private void Mute_Click(object sender, RoutedEventArgs e) private void Miniview_Click(object sender, RoutedEventArgs e)
{
if (State == PlayerDisplayState.Compact)
Maximize();
else
EnterMiniview();
}
public void UpdateVolumeIcon()
{ {
if (((GetTemplateChild("AudioMuteButton") as Button).Content as FontIcon).Glyph == "\xE74F")
Volume_ValueChanged(this, null); Volume_ValueChanged(this, null);
else
((GetTemplateChild("AudioMuteButton") as Button).Content as FontIcon).Glyph = ((GetTemplateChild("volume") as Button).Content as FontIcon).Glyph = "\xE74F";
MuteClicked?.Invoke();
} }
private void Volume_ValueChanged(object sender, RangeBaseValueChangedEventArgs e) private void Volume_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
{ {
double v = (GetTemplateChild("VolumeSlider") as Slider).Value; double v = volume.Value;
if (v == 0) if (v == 0 || Player.IsMuted)
((GetTemplateChild("AudioMuteButton") as Button).Content as FontIcon).Glyph = ((GetTemplateChild("volume") as Button).Content as FontIcon).Glyph = "\xE74F"; volumeMenu.Content = mute.Content = "\xE74F";
else if (v <= 25 && v > 0) 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) 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) 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) 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) needUpdateTimecode = false;
{ Player.Position = timecodeBackup;
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();
} }
private void Close_Click(object sender, RoutedEventArgs e) private void Close_Click(object sender, RoutedEventArgs e)
@@ -175,179 +280,243 @@ namespace FoxTube
CloseRequested?.Invoke(sender, e); CloseRequested?.Invoke(sender, e);
} }
public void SetCompactView() private void Minimize_Click(object sender, RoutedEventArgs e)
{ {
Button btn = GetTemplateChild("CompactOverlayButton") as Button; if (State == PlayerDisplayState.Normal)
(GetTemplateChild("headerToolbar") as StackPanel).Children.Remove(btn); Minimize();
(GetTemplateChild("center") as Grid).Children.Add(btn); else
Maximize();
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);
} }
public void SetMinimized() public void Minimize()
{ {
Button btnPlay = GetTemplateChild("PlayPauseButton") as Button; if (State == PlayerDisplayState.Minimized)
(GetTemplateChild("leftStack") as StackPanel).Children.Remove(btnPlay); return;
(GetTemplateChild("centralStack") as StackPanel).Children.Add(btnPlay);
Button btnFwd = GetTemplateChild("SkipForwardButton") as Button; header.Children.Remove(minimize);
(GetTemplateChild("rightStack") as StackPanel).Children.Remove(btnFwd); center.Children.Add(minimize);
(GetTemplateChild("centralStack") as StackPanel).Children.Insert(1, btnFwd); 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);
Button btnBwd = GetTemplateChild("SkipBackwardButton") as Button; header.Visibility = Visibility.Collapsed;
(GetTemplateChild("rightStack") as StackPanel).Children.Remove(btnBwd); center.Visibility = Visibility.Visible;
(GetTemplateChild("centralStack") as StackPanel).Children.Insert(0, btnBwd); footer.Visibility = Visibility.Collapsed;
(GetTemplateChild("header") as Grid).Visibility = Visibility.Collapsed; minimize.Content = "\xE010";
(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; MiniModeChanged.Invoke(this, true);
(GetTemplateChild("maximize") as Button).Visibility = Visibility.Visible; Caption.Minimize();
(GetTemplateChild("captions") as LiveCaptions).Size = 15; State = PlayerDisplayState.Minimized;
(GetTemplateChild("root") as Grid).RowDefinitions[1].Height = new GridLength(0);
} }
public void SetNormal() public void Maximize()
{ {
Button btnPlay = GetTemplateChild("PlayPauseButton") as Button; if (State == PlayerDisplayState.Normal)
(GetTemplateChild("centralStack") as StackPanel).Children.Remove(btnPlay); return;
(GetTemplateChild("leftStack") as StackPanel).Children.Insert(0, btnPlay);
Button btnFwd = GetTemplateChild("SkipForwardButton") as Button; if(State == PlayerDisplayState.Compact)
(GetTemplateChild("centralStack") as StackPanel).Children.Remove(btnFwd); {
(GetTemplateChild("rightStack") as StackPanel).Children.Insert(0, btnFwd); 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);
Button btnBwd = GetTemplateChild("SkipBackwardButton") as Button; miniview.Margin = new Thickness();
(GetTemplateChild("centralStack") as StackPanel).Children.Remove(btnBwd); miniview.Width = 50;
(GetTemplateChild("rightStack") as StackPanel).Children.Insert(0, btnBwd); 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);
(GetTemplateChild("header") as Grid).Visibility = Visibility.Visible; MiniModeChanged.Invoke(this, false);
(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) 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) if(!isReady)
{ {
queue.Enqueue(() => SetMeta(title, channel)); queue.Enqueue(() => Load(meta));
return;
}
(GetTemplateChild("title") as TextBlock).Text = title;
(GetTemplateChild("author") as TextBlock).Text = channel;
}
public void SetCaptions(IReadOnlyList<ClosedCaptionTrackInfo> list)
{
if (!isReady)
{
queue.Enqueue(() => SetCaptions(list));
return; 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) ClosedCaptions = await new YoutubeClient().GetVideoClosedCaptionTrackInfosAsync(meta.Id);
(GetTemplateChild("ccSelector") as ComboBox).Items.Add(new ComboBoxItem()
/*foreach (MuxedStreamInfo i in MediaStreams.Muxed)
quality.Items.Add(new ComboBoxItem
{
Content = $"{i.VideoQualityLabel} (muxed)",
Tag = i
});
foreach (VideoStreamInfo i in MediaStreams.Video)
quality.Items.Add(new ComboBoxItem
{
Content = $"{i.VideoQualityLabel} (video-only)",
Tag = i
});
foreach (AudioStreamInfo i in MediaStreams.Audio)
quality.Items.Add(new ComboBoxItem
{
Content = $"{i.Bitrate} (audio-only)",
Tag = i
});*/
uint screenHeight = DisplayInformation.GetForCurrentView().ScreenHeightInRawPixels;
List<string> qualityList = MediaStreams.GetAllVideoQualityLabels().ToList();
qualityList.Sort(new QualityComparer());
qualityList.Reverse();
quality.Items.Add(new ComboBoxItem
{
Content = ResourceLoader.GetForCurrentView("VideoPage").GetString("/VideoPage/auto")
});
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() == "mp4"))
tag = MediaStreams.Video.Find(m => m.VideoQualityLabel == i && m.Container.GetFileExtension() == "mp4");
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") : ""), Content = string.Format("{0} {1}", CultureInfo.GetCultureInfo(i.Language.Code).DisplayName, i.IsAutoGenerated ? ResourceLoader.GetForCurrentView("VideoPage").GetString("/VideoPage/generatedCaption") : ""),
Tag = i 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) 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 else
(GetTemplateChild("cc") as Button).Visibility = Visibility.Collapsed;
}
public void SetQualities(MediaStreamInfoSet list)
{ {
if (!isReady) captionsMenu.Visibility = Visibility.Collapsed;
seek.Visibility = Visibility.Collapsed;
live.Visibility = Visibility.Visible;
remain.Visibility = Visibility.Collapsed;
elapsed.FontSize = 24;
Grid.SetRow(elapsed, 0);
Grid.SetRowSpan(elapsed, 2);
elapsed.HorizontalAlignment = HorizontalAlignment.Right;
fwd.Visibility = Visibility.Collapsed;
bwd.Visibility = Visibility.Collapsed;
List<StreamQuality> list = await ManifestGenerator.ResolveLiveSteream(MediaStreams.HlsLiveStreamUrl);
foreach (StreamQuality i in list)
quality.Items.Add(new ComboBoxItem
{ {
queue.Enqueue(() => SetQualities(list)); Content = i.Resolution,
return; Tag = i
} });
MediaStreams = list;
List<VideoQuality> q = list.GetAllVideoQualities().ToList();
q.Sort();
q.Reverse();
List<string> labels = new List<string>();
q.ForEach(i => labels.Add(i.GetVideoQualityLabel()));
(GetTemplateChild("qualitySelector") as ComboBox).ItemsSource = labels;
string s = SettingsStorage.VideoQuality == "remember" ? SettingsStorage.RememberedQuality : SettingsStorage.VideoQuality; string s = SettingsStorage.VideoQuality == "remember" ? SettingsStorage.RememberedQuality : SettingsStorage.VideoQuality;
if (labels.Contains(s)) if (quality.Items.Any(i => (i as ComboBoxItem).Content as string == s))
(GetTemplateChild("qualitySelector") as ComboBox).SelectedItem = s; quality.SelectedItem = quality.Items.Find(i => (i as ComboBoxItem).Content as string == s);
else else
(GetTemplateChild("qualitySelector") as ComboBox).SelectedIndex = 0; quality.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 = url.ToUri();
} }
} }
} }
+4 -4
View File
@@ -8,10 +8,11 @@
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="1080" d:DesignHeight="1080"
d:DesignWidth="1920" d:DesignWidth="1920"
RequestedTheme="Dark"> RequestedTheme="Dark"
Visibility="Collapsed">
<Grid Background="White" Name="grid"> <Grid Background="{StaticResource SystemChromeMediumColor}">
<MediaElement Name="videoSource" MarkerReached="VideoSource_MarkerReached" AreTransportControlsEnabled="True" PosterSource="ms-appx:///Assets/videoThumbSample.png" CurrentStateChanged="VideoSource_CurrentStateChanged" MediaOpened="VideoSource_MediaOpened" VolumeChanged="VideoSource_VolumeChanged"> <MediaElement Name="videoSource" AreTransportControlsEnabled="True" VolumeChanged="VideoSource_VolumeChanged" CurrentStateChanged="VideoSource_CurrentStateChanged" MarkerReached="VideoSource_MarkerReached" PosterSource="ms-appx:///Assets/videoThumbSample.png">
<MediaElement.TransportControls> <MediaElement.TransportControls>
<foxtube:PlayerControls IsCompactOverlayButtonVisible="True" IsCompactOverlayEnabled="True" <foxtube:PlayerControls IsCompactOverlayButtonVisible="True" IsCompactOverlayEnabled="True"
IsFullWindowButtonVisible="True" IsFullWindowEnabled="True" IsFullWindowButtonVisible="True" IsFullWindowEnabled="True"
@@ -19,6 +20,5 @@
IsSkipForwardButtonVisible="True" IsSkipForwardEnabled="True"/> IsSkipForwardButtonVisible="True" IsSkipForwardEnabled="True"/>
</MediaElement.TransportControls> </MediaElement.TransportControls>
</MediaElement> </MediaElement>
<MediaElement Name="audioSource" Width="0" AreTransportControlsEnabled="False" Visibility="Collapsed" Height="0" VerticalAlignment="Top" HorizontalAlignment="Left" CurrentStateChanged="AudioSource_CurrentStateChanged" MediaOpened="AudioSource_MediaOpened"/>
</Grid> </Grid>
</UserControl> </UserControl>
+64 -126
View File
@@ -1,5 +1,4 @@
using System; using System;
using System.Linq;
using Windows.UI.Core; using Windows.UI.Core;
using Windows.UI.Xaml; using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls;
@@ -9,7 +8,19 @@ using Windows.Media;
using Windows.Storage.Streams; using Windows.Storage.Streams;
using YoutubeExplode.Models.MediaStreams; using YoutubeExplode.Models.MediaStreams;
using YoutubeExplode; using YoutubeExplode;
using System.IO;
using FoxTube.Classes;
using Windows.Media.Core;
using System.Linq;
using Windows.Media.Playback;
using System.Threading.Tasks;
using Windows.Media.Editing;
using Windows.Storage.Pickers;
using Windows.Storage;
using Windows.Media.MediaProperties;
using FoxTube.Controls.Player;
using System.Diagnostics; using System.Diagnostics;
using Windows.UI.Xaml.Media;
namespace FoxTube namespace FoxTube
{ {
@@ -17,19 +28,14 @@ namespace FoxTube
{ {
public Video item; public Video item;
public string avatar; public string avatar;
public HistoryItem history;
bool incognito = false;
public event Event NextClicked; public event Event NextClicked;
public event ObjectEventHandler MiniMode; public event ObjectEventHandler MiniMode;
public PlayerControls Controls => videoSource.TransportControls as PlayerControls; public PlayerControls Controls => videoSource.TransportControls as PlayerControls;
public TimeSpan Position => videoSource.Position; public MediaElement Player => videoSource;
bool audioLoaded = false;
bool videoLoaded = false;
bool isMuxed = false;
DispatcherTimer muxedTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(100) };
TimeSpan timecodeBackup;
bool needUpdateTimecode = false;
SystemMediaTransportControls systemControls; SystemMediaTransportControls systemControls;
@@ -38,60 +44,51 @@ namespace FoxTube
InitializeComponent(); 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; item = meta;
avatar = channelAvatar; avatar = channelAvatar;
history = new HistoryItem
{
Id = item.Id
};
videoSource.PosterSource = new BitmapImage((meta.Snippet.Thumbnails.Maxres ?? meta.Snippet.Thumbnails.Medium).Url.ToUri()); 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") if (item.Snippet.LiveBroadcastContent == "none")
{ {
InitializeContols(); InitializeContols();
Controls.Load(item);
if (Methods.GetDuration(item.ContentDetails.Duration).TotalMinutes > 5) if (Methods.GetDuration(item.ContentDetails.Duration).TotalMinutes > 5)
videoSource.Markers.Add(new Windows.UI.Xaml.Media.TimelineMarker { Time = Methods.GetDuration(item.ContentDetails.Duration) - TimeSpan.FromMinutes(1) }); videoSource.Markers.Add(new TimelineMarker { Time = Methods.GetDuration(item.ContentDetails.Duration) - TimeSpan.FromMinutes(1) });
Controls.SetQualities(await new YoutubeClient().GetVideoMediaStreamInfosAsync(item.Id)); if(Methods.GetDuration(item.ContentDetails.Duration).TotalMinutes >= 60)
Controls.SetCaptions(await new YoutubeClient().GetVideoClosedCaptionTrackInfosAsync(item.Id)); for (int k = 1; k < Methods.GetDuration(item.ContentDetails.Duration).TotalMinutes / 30; k++)
videoSource.Markers.Add(new TimelineMarker { Time = TimeSpan.FromMinutes(k * 30) });
if (!privateMode)
HistorySet.Update(history);
} }
else if (item.Snippet.LiveBroadcastContent == "live") else if (item.Snippet.LiveBroadcastContent == "live")
{ {
InitializeContols(); InitializeContols();
Controls.IsSkipBackwardButtonVisible = false; Controls.Load(item);
Controls.IsSkipForwardButtonVisible = false;
Controls.LiveRequested += Controls_LiveRequested;
Controls.SetStream((await new YoutubeClient().GetVideoMediaStreamInfosAsync(item.Id)).HlsLiveStreamUrl);
} }
else else
videoSource.AreTransportControlsEnabled = false; videoSource.AreTransportControlsEnabled = false;
if (!privateMode)
Debug.WriteLine("TODO: history entry creation");
// TODO: Create history entry
Visibility = Visibility.Visible; Visibility = Visibility.Visible;
} }
private void Controls_LiveRequested(object sender, RoutedEventArgs e)
{
videoSource.Position = videoSource.NaturalDuration.TimeSpan;
}
public void InitializeContols() public void InitializeContols()
{ {
videoSource.Volume = SettingsStorage.Volume; videoSource.Volume = SettingsStorage.Volume;
videoSource.AutoPlay = SettingsStorage.Autoplay;
muxedTimer.Tick += (s, e) =>
{
if (!Enumerable.Range(-100, 100).Contains((int)(videoSource.Position - audioSource.Position).TotalMilliseconds))
audioSource.Position = videoSource.Position;
};
Controls.CloseRequested += Controls_CloseRequested; Controls.CloseRequested += Controls_CloseRequested;
Controls.NextRequested += (s, e) => NextClicked?.Invoke(); Controls.NextRequested += (s, e) => NextClicked?.Invoke();
Controls.QualityChanged += Controls_QualityChanged;
Controls.MiniModeChanged += Controls_MiniModeChanged; Controls.MiniModeChanged += Controls_MiniModeChanged;
Controls.MuteClicked += Controls_MuteClicked;
Controls.Player = videoSource; Controls.Player = videoSource;
#region System Media Transport Controls #region System Media Transport Controls
@@ -111,10 +108,23 @@ namespace FoxTube
#endregion #endregion
} }
private void Controls_MuteClicked() private async void SystemControls_Engaged(SystemMediaTransportControls sender, SystemMediaTransportControlsButtonPressedEventArgs args)
{ {
if (audioSource != null) await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
audioSource.IsMuted = videoSource.IsMuted; {
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) public void Controls_MiniModeChanged(object sender, bool e)
@@ -129,60 +139,21 @@ namespace FoxTube
Controls.Minimize(); Controls.Minimize();
} }
private void Controls_QualityChanged(object sender, MediaStreamInfo requestedQuality, MediaStreamInfoSet list)
{
videoSource.Pause();
timecodeBackup = videoSource.Position;
needUpdateTimecode = true;
audioLoaded = false;
videoLoaded = false;
muxedTimer.Stop();
videoSource.Source = requestedQuality.Url.ToUri();
if(requestedQuality is MuxedStreamInfo)
{
isMuxed = true;
audioSource.Source = null;
}
else
audioSource.Source = list.Audio.First().Url.ToUri();
}
public void Controls_CloseRequested(object sender, RoutedEventArgs e) public void Controls_CloseRequested(object sender, RoutedEventArgs e)
{ {
if(systemControls != null) videoSource.Pause();
systemControls.IsEnabled = false; systemControls.IsEnabled = false;
videoSource.Source = null;
videoSource.Stop(); if (!incognito && item.Snippet.LiveBroadcastContent == "none")
audioSource.Stop(); {
history.LeftOn = videoSource.Position;
HistorySet.Update(history);
}
Methods.MainPage.CloseVideo(); Methods.MainPage.CloseVideo();
} }
private async void SystemControls_Engaged(SystemMediaTransportControls sender, SystemMediaTransportControlsButtonPressedEventArgs args)
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
if (args.Button == SystemMediaTransportControlsButton.Next)
NextClicked?.Invoke();
switch (args.Button)
{
case SystemMediaTransportControlsButton.Play:
videoSource.Play();
break;
case SystemMediaTransportControlsButton.Pause:
videoSource.Pause();
break;
case SystemMediaTransportControlsButton.Next:
NextClicked.Invoke();
break;
}
});
}
public void Pause() public void Pause()
{ {
videoSource.Pause(); videoSource.Pause();
@@ -190,57 +161,24 @@ namespace FoxTube
private void VideoSource_CurrentStateChanged(object sender, RoutedEventArgs e) private void VideoSource_CurrentStateChanged(object sender, RoutedEventArgs e)
{ {
switch(videoSource.CurrentState) systemControls.PlaybackStatus = videoSource.CurrentState == MediaElementState.Playing ? MediaPlaybackStatus.Playing : MediaPlaybackStatus.Paused;
{
case Windows.UI.Xaml.Media.MediaElementState.Buffering:
case Windows.UI.Xaml.Media.MediaElementState.Paused:
systemControls.PlaybackStatus = MediaPlaybackStatus.Paused;
// TODO: Create history entry
audioSource?.Pause();
break;
case Windows.UI.Xaml.Media.MediaElementState.Playing:
systemControls.PlaybackStatus = MediaPlaybackStatus.Playing;
audioSource?.Play();
break;
}
}
private void AudioSource_CurrentStateChanged(object sender, RoutedEventArgs e) if(videoSource.CurrentState == MediaElementState.Paused)
if (!incognito && item.Snippet.LiveBroadcastContent == "none")
{ {
if(audioSource.CurrentState == Windows.UI.Xaml.Media.MediaElementState.Playing) history.LeftOn = videoSource.Position;
muxedTimer.Start(); HistorySet.Update(history);
else
muxedTimer.Stop();
} }
private void VideoSource_MediaOpened(object sender, RoutedEventArgs e)
{
videoLoaded = true;
if (needUpdateTimecode)
{
videoSource.Position = timecodeBackup;
needUpdateTimecode = false;
}
if (audioLoaded || isMuxed)
videoSource.Play();
}
private void AudioSource_MediaOpened(object sender, RoutedEventArgs e)
{
audioLoaded = true;
if (needUpdateTimecode)
audioSource.Position = timecodeBackup;
if (videoLoaded)
videoSource.Play();
} }
private void VideoSource_VolumeChanged(object sender, RoutedEventArgs e) private void VideoSource_VolumeChanged(object sender, RoutedEventArgs e)
{ {
audioSource.Volume = videoSource.Volume; if(videoSource.Volume != 0)
SettingsStorage.Volume = videoSource.Volume; SettingsStorage.Volume = videoSource.Volume;
Controls.UpdateVolumeIcon();
} }
private void VideoSource_MarkerReached(object sender, Windows.UI.Xaml.Media.TimelineMarkerRoutedEventArgs e) private void VideoSource_MarkerReached(object sender, TimelineMarkerRoutedEventArgs e)
{ {
Controls.Advert.PushAdvert(); Controls.Advert.PushAdvert();
} }
+1 -1
View File
@@ -72,7 +72,7 @@
<UserControl.ContextFlyout> <UserControl.ContextFlyout>
<MenuFlyout> <MenuFlyout>
<MenuFlyoutItem x:Uid="/Cards/play" Icon="Play" Text="Play" Name="play" Click="Button_Click"/> <MenuFlyoutItem x:Uid="/Cards/play" Icon="Play" Text="Play" Name="play" Click="Button_Click"/>
<MenuFlyoutItem x:Uid="/Cards/incognitoPlay" Text="Play incognito" Visibility="Collapsed"> <MenuFlyoutItem x:Uid="/Cards/incognitoPlay" Text="Play incognito" Name="incognito" Click="Button_Click">
<MenuFlyoutItem.Icon> <MenuFlyoutItem.Icon>
<FontIcon Glyph="&#xE727;"/> <FontIcon Glyph="&#xE727;"/>
</MenuFlyoutItem.Icon> </MenuFlyoutItem.Icon>
+54 -2
View File
@@ -27,6 +27,7 @@ namespace FoxTube.Controls
public string playlistId; public string playlistId;
public string videoId; public string videoId;
Video item; Video item;
HistoryItem history;
public VideoCard(string id, string playlist = null) public VideoCard(string id, string playlist = null)
{ {
@@ -34,6 +35,7 @@ namespace FoxTube.Controls
Initialize(id, playlist); Initialize(id, playlist);
} }
public VideoCard(Video meta, string playlist = null) public VideoCard(Video meta, string playlist = null)
{ {
InitializeComponent(); InitializeComponent();
@@ -105,6 +107,12 @@ namespace FoxTube.Controls
if (SecretsVault.History.Contains(videoId)) if (SecretsVault.History.Contains(videoId))
watched.Visibility = Visibility.Visible; 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;
}
show.Begin(); show.Begin();
} }
@@ -196,6 +204,8 @@ namespace FoxTube.Controls
if (SecretsVault.History.Contains(videoId)) if (SecretsVault.History.Contains(videoId))
watched.Visibility = Visibility.Visible; watched.Visibility = Visibility.Visible;
if (HistorySet.Items.Exists(i => i.Id == item.Id))
leftOn.Value = 100 * HistorySet.Items.Find(i => i.Id == item.Id).LeftOn.TotalSeconds / Methods.GetDuration(item.ContentDetails.Duration).TotalSeconds;
show.Begin(); show.Begin();
} }
@@ -234,7 +244,7 @@ namespace FoxTube.Controls
} }
} }
Methods.MainPage.GoToVideo(videoId, playlistId); Methods.MainPage.GoToVideo(videoId, playlistId == "HL" ? null : playlistId, ((FrameworkElement)sender).Name == "incognito" ? true : false);
} }
private void Share(DataTransferManager sender, DataRequestedEventArgs args) private void Share(DataTransferManager sender, DataRequestedEventArgs args)
@@ -358,9 +368,35 @@ namespace FoxTube.Controls
Item_Click(menuItem, null); Item_Click(menuItem, null);
} }
private void Wl_Click(object sender, RoutedEventArgs e) private async void Wl_Click(object sender, RoutedEventArgs e)
{ {
if (wl.IsChecked)
{
try
{
PlaylistItem playlist = new PlaylistItem
{
Snippet = new PlaylistItemSnippet
{
PlaylistId = "WL",
ResourceId = new ResourceId
{
VideoId = item.Id,
Kind = "youtube#video"
}
}
};
PlaylistItemsResource.InsertRequest request = SecretsVault.Service.PlaylistItems.Insert(playlist, "snippet");
await request.ExecuteAsync();
}
catch
{
wl.IsChecked = false;
}
}
else
wl.IsChecked = true;
} }
async void LoadAddTo() async void LoadAddTo()
@@ -449,6 +485,22 @@ namespace FoxTube.Controls
private async void Delete_Click(object sender, RoutedEventArgs e) private async void Delete_Click(object sender, RoutedEventArgs e)
{ {
if (playlistId == "WL")
{
await Launcher.LaunchUriAsync("https://youtube.com/playlist?list=WL".ToUri());
return;
}
else if(playlistId == "HL")
{
if (history == null)
await Launcher.LaunchUriAsync("https://youtube.com/feed/history".ToUri());
else
{
HistorySet.Delete(history);
(Methods.MainPage.PageContent as History).Delete(this);
}
return;
}
try try
{ {
PlaylistItemsResource.ListRequest request = SecretsVault.Service.PlaylistItems.List("snippet"); PlaylistItemsResource.ListRequest request = SecretsVault.Service.PlaylistItems.List("snippet");
+4 -3
View File
@@ -21,7 +21,7 @@
<PackageCertificateThumbprint>50B93E6A246058D555BA65CD203D7A02064A7409</PackageCertificateThumbprint> <PackageCertificateThumbprint>50B93E6A246058D555BA65CD203D7A02064A7409</PackageCertificateThumbprint>
<GenerateAppInstallerFile>False</GenerateAppInstallerFile> <GenerateAppInstallerFile>False</GenerateAppInstallerFile>
<AppxAutoIncrementPackageRevision>True</AppxAutoIncrementPackageRevision> <AppxAutoIncrementPackageRevision>True</AppxAutoIncrementPackageRevision>
<AppxPackageDir>E:\XFox\Documents\FoxTube builds\0.4\</AppxPackageDir> <AppxPackageDir>E:\XFox\Documents\FoxTube builds\0.5\</AppxPackageDir>
<AppxBundle>Always</AppxBundle> <AppxBundle>Always</AppxBundle>
<AppxBundlePlatforms>x86|x64|arm</AppxBundlePlatforms> <AppxBundlePlatforms>x86|x64|arm</AppxBundlePlatforms>
<AppInstallerUpdateFrequency>1</AppInstallerUpdateFrequency> <AppInstallerUpdateFrequency>1</AppInstallerUpdateFrequency>
@@ -107,6 +107,7 @@
<Compile Include="Classes\Methods.cs" /> <Compile Include="Classes\Methods.cs" />
<Compile Include="Classes\SearchPaameters.cs" /> <Compile Include="Classes\SearchPaameters.cs" />
<Compile Include="Classes\SettingsStorage.cs" /> <Compile Include="Classes\SettingsStorage.cs" />
<Compile Include="Classes\ManifestGenerator.cs" />
<Compile Include="Controls\Advert.xaml.cs"> <Compile Include="Controls\Advert.xaml.cs">
<DependentUpon>Advert.xaml</DependentUpon> <DependentUpon>Advert.xaml</DependentUpon>
</Compile> </Compile>
@@ -429,7 +430,7 @@
<Version>10.1811.22001</Version> <Version>10.1811.22001</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.AppCenter.Analytics"> <PackageReference Include="Microsoft.AppCenter.Analytics">
<Version>1.14.0</Version> <Version>2.0.0</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform"> <PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
<Version>6.2.8</Version> <Version>6.2.8</Version>
@@ -450,7 +451,7 @@
<Version>4.3.2</Version> <Version>4.3.2</Version>
</PackageReference> </PackageReference>
<PackageReference Include="YoutubeExplode"> <PackageReference Include="YoutubeExplode">
<Version>4.6.8</Version> <Version>4.7.0</Version>
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3" IgnorableNamespaces="uap mp uap3"> <Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3" IgnorableNamespaces="uap mp uap3">
<Identity Name="53949MichaelXFoxGordeev.FoxTube" Publisher="CN=FD7A34DD-FE4D-4D7D-9D33-2DA9EBBE7725" Version="0.4.8.0" /> <Identity Name="53949MichaelXFoxGordeev.FoxTube" Publisher="CN=FD7A34DD-FE4D-4D7D-9D33-2DA9EBBE7725" Version="0.6.0.0" />
<mp:PhoneIdentity PhoneProductId="04fd81c1-6473-4174-afd7-4ac71dd85721" PhonePublisherId="00000000-0000-0000-0000-000000000000" /> <mp:PhoneIdentity PhoneProductId="04fd81c1-6473-4174-afd7-4ac71dd85721" PhonePublisherId="00000000-0000-0000-0000-000000000000" />
<Properties> <Properties>
<DisplayName>FoxTube</DisplayName> <DisplayName>FoxTube</DisplayName>
+28 -5
View File
@@ -1,4 +1,8 @@
using Windows.UI.Xaml.Controls; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using Windows.UI.Xaml.Controls;
namespace FoxTube.Pages namespace FoxTube.Pages
{ {
@@ -11,10 +15,29 @@ namespace FoxTube.Pages
public async void Initialize() public async void Initialize()
{ {
SecretsVault.HttpClient.DefaultRequestHeaders.Referrer = "https://youtube.com/".ToUri(); Dictionary<string, string> postContent = new Dictionary<string, string>
string response = await SecretsVault.Service.HttpClient.GetStringAsync(adress.Text); {
code.Text = response; { "sej", "{\"clickTrackingParams\":\"COQBEJQ1GAIiEwi028HJkdrhAhWCU5sKHQVmCS0ojh4=\",\"commandMetadata\":{\"webCommandMetadata\":{\"url\":\"/service_ajax\",\"sendPost\":true}},\"playlistEditEndpoint\":{\"playlistId\":\"WL\",\"actions\":[{\"action\":\"ACTION_REMOVE_VIDEO_BY_VIDEO_ID\",\"removedVideoId\":\"_j6Os3lIQ4U\"}]}}" },
//view.NavigateToString(response); { "csn", "2K-4XLSPG4Kn7QSFzKXoAg" },
{ "session_token", "QUFFLUhqbVF6RkxPeWI3Z2s0bGJhMExMcjNmQ3BNRG9LUXxBQ3Jtc0tuR1pwOFJGMVd2ZGplSC1tTlZXd3M5bGxiZmdCR0pDZUEta0QyN3poejZCMlI2T1pfQXFnRU1yYkc4bzk5d0hrRUZ3bWlCbVZtWFl2U0h4UUp6RS1QRmlHVmNmTDVaeExGSF9ORXFCTko4ZEp0VnBlRVJnaWNRN2VQV05GaksyZm9MZTBnLTVRYWxnandGclVpSmZteU9fTjVxNUE=" }
};
FormUrlEncodedContent encoded = new FormUrlEncodedContent(postContent);
string encodedString = await encoded.ReadAsStringAsync();
//TODO: Fuck this shit
Dictionary<string, string> header = new Dictionary<string, string>
{
{ "Cookie", "CONSENT=YES+RU.ru+20180708-12-1; PREF=f6=400&al=ru&f1=50000000; VISITOR_INFO1_LIVE=ZDgD76zHoGM; SID=TwcUCkXNM6kCpxo-8TS_8h1W5Mc7v9InTeaSc2pUhTrhtJYT3370p3EGjMt7V_zIQCsT7w.; HSID=AKgfrRj-NlJVFHs_f; SSID=A8Ix_30UlJTHAZ_wi; APISID=mhnP6tU_JCLaQbsf/AcDBnYqdpqugTs5Uv; SAPISID=Y8OdALP87rADrHbx/Ak4TOmVQHJrSt5k9G; LOGIN_INFO=AFmmF2swRQIhALLDYwBvigvNfzj2iZ5FSy4EL9BhyUqFOuTKSu00jFJfAiAi6ej1ruClPwY6h8yMALu8zplkxU1g9HO8b1gMjdXJdg:QUQ3MjNmd2RFZFNfanB2OHpBVXRTWEs5cUo1Q1NVS0NjTV9ZUG1FQzJuam95S2t4Mm0yazU1ckYxdFN2S3dSTnlRaWZnWXBhYmFTWm1PWlAzVXNXX2dzYWtLUWdJSHgycG5Ed1R4bUJEdG1Hb2ZlR1JtRkI5QnhjYzhCZkNQNDhydWdkWUtjYl9MdVBOM0Y0RDRNNEdHRXZTNllKek9UT1hQMFJIdFpMcnFWNTh2NEtDS0FFdFNJ; SIDCC=AN0-TYv_HcQWprTqyxz1eROShbpRLucFOUPyiHv8XagpVwb_GvBvRAN4MClzKZ5dUj2ptf1NWyE; YSC=6ZnsQM5SW1A; ST-893xe3=itct=COQBEJQ1GAIiEwi028HJkdrhAhWCU5sKHQVmCS0ojh4yCmctaGlnaC1yZWNaD0ZFd2hhdF90b193YXRjaA%3D%3D&csn=2K-4XLSPG4Kn7QSFzKXoAg; ST-1w9u2il=itct=COQBEJQ1GAIiEwi028HJkdrhAhWCU5sKHQVmCS0ojh4%3D&csn=2K-4XLSPG4Kn7QSFzKXoAg" },
};
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "https://www.youtube.com/service_ajax?name=playlistEditEndpoint");
request.Content = encoded;
//request.Headers.Add("Cookie", "CONSENT=YES+RU.ru+20180708-12-1; PREF=f6=400&al=ru&f1=50000000; VISITOR_INFO1_LIVE=ZDgD76zHoGM; SID=TwcUCkXNM6kCpxo-8TS_8h1W5Mc7v9InTeaSc2pUhTrhtJYT3370p3EGjMt7V_zIQCsT7w.; HSID=AKgfrRj-NlJVFHs_f; SSID=A8Ix_30UlJTHAZ_wi; APISID=mhnP6tU_JCLaQbsf/AcDBnYqdpqugTs5Uv; SAPISID=Y8OdALP87rADrHbx/Ak4TOmVQHJrSt5k9G; LOGIN_INFO=AFmmF2swRQIhALLDYwBvigvNfzj2iZ5FSy4EL9BhyUqFOuTKSu00jFJfAiAi6ej1ruClPwY6h8yMALu8zplkxU1g9HO8b1gMjdXJdg:QUQ3MjNmd2RFZFNfanB2OHpBVXRTWEs5cUo1Q1NVS0NjTV9ZUG1FQzJuam95S2t4Mm0yazU1ckYxdFN2S3dSTnlRaWZnWXBhYmFTWm1PWlAzVXNXX2dzYWtLUWdJSHgycG5Ed1R4bUJEdG1Hb2ZlR1JtRkI5QnhjYzhCZkNQNDhydWdkWUtjYl9MdVBOM0Y0RDRNNEdHRXZTNllKek9UT1hQMFJIdFpMcnFWNTh2NEtDS0FFdFNJ; SIDCC=AN0-TYv_HcQWprTqyxz1eROShbpRLucFOUPyiHv8XagpVwb_GvBvRAN4MClzKZ5dUj2ptf1NWyE; YSC=6ZnsQM5SW1A; ST-893xe3=itct=COQBEJQ1GAIiEwi028HJkdrhAhWCU5sKHQVmCS0ojh4yCmctaGlnaC1yZWNaD0ZFd2hhdF90b193YXRjaA%3D%3D&csn=2K-4XLSPG4Kn7QSFzKXoAg; ST-1w9u2il=itct=COQBEJQ1GAIiEwi028HJkdrhAhWCU5sKHQVmCS0ojh4%3D&csn=2K-4XLSPG4Kn7QSFzKXoAg");
//request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", SecretsVault.Credential.Token.AccessToken);
HttpResponseMessage response = await new HttpClient().SendAsync(request);
Debug.WriteLine("Done");
} }
private void Adress_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args) private void Adress_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)
+45 -4
View File
@@ -15,16 +15,57 @@
<RowDefinition/> <RowDefinition/>
<RowDefinition Height="auto"/> <RowDefinition Height="auto"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Pivot SelectionChanged="Pivot_SelectionChanged">
<PivotItem x:Uid="/Playlist/appHistory" Header="Application">
<Grid>
<ScrollViewer> <ScrollViewer>
<StackPanel>
<Grid Margin="0,10" BorderBrush="Red" BorderThickness="5" CornerRadius="10" Padding="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<FontIcon Glyph="&#xF6FA;" FontSize="50" VerticalAlignment="Center"/>
<StackPanel Grid.Column="1" Margin="10,0">
<TextBlock x:Uid="/Playlist/hlHeader" FontWeight="Bold" FontSize="25" Text="Missing some stuff?" VerticalAlignment="Center"/>
<TextBlock x:Uid="/Playlist/hlBody" Text="Unfortunately, for now we are unable to manage your youtube history. If you want to see your web history go to 'Website' tab" TextWrapping="WrapWholeWords"/>
</StackPanel>
</Grid>
<Button Margin="5" x:Uid="/Playlist/clear" FontFamily="Segoe UI, Segoe MDL2 Assets" Content="&#xE107; Clear history" Name="clear" Click="Clear_Click"/>
<local:VideoGrid x:Name="list"/>
<controls:ShowMore x:Name="more" Clicked="ShowMore_Clicked"/>
</StackPanel>
</ScrollViewer>
<foxtube:LoadingPage x:Name="loading" Visibility="Collapsed" RefreshPage="Refresh_Click"/>
</Grid>
</PivotItem>
<PivotItem x:Uid="/Playlist/webHistory" Header="Website">
<Grid>
<ScrollViewer>
<StackPanel>
<local:VideoGrid x:Name="websiteList"/>
<controls:ShowMore x:Name="websiteMore" Clicked="WebsiteMore_Clicked"/>
</StackPanel>
</ScrollViewer>
<foxtube:LoadingPage x:Name="websiteLoading" Visibility="Collapsed" RefreshPage="Refresh_Click"/>
</Grid>
</PivotItem>
</Pivot>
<!--<ScrollViewer>
<StackPanel> <StackPanel>
<local:VideoGrid x:Name="list"/> <local:VideoGrid x:Name="list"/>
<controls:ShowMore Clicked="ShowMore_Clicked" x:Name="more"/> <controls:ShowMore Clicked="ShowMore_Clicked" x:Name="more"/>
</StackPanel> </StackPanel>
</ScrollViewer> </ScrollViewer>-->
<CommandBar Grid.Row="1" DefaultLabelPosition="Right"> <CommandBar Grid.Row="1" DefaultLabelPosition="Right">
<AppBarButton Icon="Refresh" Label="Refresh" Name="refresh" Click="Refresh_Click"/> <AppBarButton x:Uid="/Playlist/refresh" Icon="Refresh" Label="Refresh" Name="refresh" Click="Refresh_Click"/>
<AppBarButton Label="Open in browser" Icon="Globe" Name="toBrowser" Click="toBrowser_Click"/> <AppBarButton x:Uid="/Playlist/openWeb" Label="Open in browser" Icon="Globe" Name="toBrowser" Click="toBrowser_Click"/>
</CommandBar> </CommandBar>
<foxtube:LoadingPage Visibility="Collapsed" Grid.RowSpan="2" x:Name="loading"/>
</Grid> </Grid>
</Page> </Page>
+97 -26
View File
@@ -2,7 +2,9 @@
using Microsoft.AppCenter.Analytics; using Microsoft.AppCenter.Analytics;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Windows.ApplicationModel.Resources;
using Windows.System; using Windows.System;
using Windows.UI.Popups;
using Windows.UI.Xaml; using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation; using Windows.UI.Xaml.Navigation;
@@ -15,9 +17,8 @@ namespace FoxTube.Pages
public sealed partial class History : Page, NavigationPage public sealed partial class History : Page, NavigationPage
{ {
public object Parameter { get; set; } = null; public object Parameter { get; set; } = null;
List<string> entries;
int page = 1; int page = 1;
public string id = "HL"; int webPage = 0;
public History() public History()
{ {
@@ -29,11 +30,6 @@ namespace FoxTube.Pages
base.OnNavigatedTo(e); base.OnNavigatedTo(e);
Parameter = e.Parameter; Parameter = e.Parameter;
loading.RefreshPage += Refresh_Click;
if (!string.IsNullOrWhiteSpace(e.Parameter.ToString()))
id = e.Parameter.ToString();
Initialize(); Initialize();
} }
@@ -43,52 +39,127 @@ namespace FoxTube.Pages
{ {
loading.Refresh(); loading.Refresh();
entries = id == "HL" ? SecretsVault.History = await Methods.GetHistory() : SecretsVault.WatchLater = await Methods.GetLater(); await Dispatcher.RunIdleAsync((command) =>
{
for (int k = 0; k < 25 && k < HistorySet.Items.Count; k++)
list.Add(new VideoCard(HistorySet.Items[k].Id, "HL"));
for (int k = 0; k < 25 && k < entries.Count; k++) if (list.Count >= HistorySet.Items.Count)
list.Add(new VideoCard(entries[k]));
if (list.Count >= entries.Count)
more.Visibility = Visibility.Collapsed; more.Visibility = Visibility.Collapsed;
if (list.Count == 0)
clear.Visibility = Visibility.Collapsed;
});
loading.Close(); loading.Close();
} }
catch (System.Net.Http.HttpRequestException)
{
loading.Error("System.Net.Http.HttpRequestException", "Unable to connect to Google servers.", true);
}
catch (Exception e) catch (Exception e)
{ {
loading.Error(e.GetType().ToString(), e.Message); loading.Error(e.GetType().ToString(), e.Message);
Analytics.TrackEvent("History loading error", new Dictionary<string, string>() Analytics.TrackEvent("Local history loading error", new Dictionary<string, string>()
{ {
{ "Exception", e.GetType().ToString() }, { "Exception", e.GetType().ToString() },
{ "Message", e.Message }, { "Message", e.Message }
{ "ID", id }
}); });
} }
} }
private async void toBrowser_Click(object sender, RoutedEventArgs e) private async void toBrowser_Click(object sender, RoutedEventArgs e)
{ {
await Launcher.LaunchUriAsync(new Uri(id == "HL" ? "https://www.youtube.com/feed/history" : "https://www.youtube.com/playlist?list=WL")); await Launcher.LaunchUriAsync("https://www.youtube.com/feed/history".ToUri());
} }
private void Refresh_Click(object sender, RoutedEventArgs e) private void Refresh_Click(object sender, RoutedEventArgs e)
{ {
list.Clear(); Methods.MainPage.GoToHistory();
Initialize();
} }
private void ShowMore_Clicked() private async void ShowMore_Clicked()
{ {
for (int k = 25 * page++; k < 25 * page; k++) await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
list.Add(new VideoCard(entries[k])); {
for (int k = 25 * page++; k < 25 * page && k < HistorySet.Items.Count; k++)
list.Add(new VideoCard(HistorySet.Items[k].Id, "HL"));
if (list.Count >= entries.Count) if (list.Count >= HistorySet.Items.Count)
more.Visibility = Visibility.Collapsed; more.Visibility = Visibility.Collapsed;
else else
more.Complete(); more.Complete();
});
}
private async void Clear_Click(object sender, RoutedEventArgs e)
{
ResourceLoader resources = ResourceLoader.GetForCurrentView("Playlist");
MessageDialog dialog = new MessageDialog(resources.GetString("/Playlist/historyClear/Body"), resources.GetString("/Playlist/historyClear/Header"));
dialog.Commands.Add(new UICommand(resources.GetString("/Playlist/no")));
dialog.Commands.Add(new UICommand(resources.GetString("/Playlist/yes"), (command) =>
{
HistorySet.Clear();
Refresh_Click(this, null);
}));
dialog.CancelCommandIndex = 0;
dialog.DefaultCommandIndex = 1;
await dialog.ShowAsync();
}
private void Pivot_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if(webPage == 0)
{
webPage++;
LoadWeb();
}
}
async void LoadWeb()
{
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async () =>
{
try
{
websiteLoading.Refresh();
SecretsVault.History = await Methods.GetHistory();
for (int k = 0; k < 25 && k < SecretsVault.History.Count; k++)
websiteList.Add(new VideoCard(SecretsVault.History[k], "HL"));
if (websiteList.Count >= SecretsVault.History.Count)
websiteMore.Visibility = Visibility.Collapsed;
websiteLoading.Close();
}
catch (Exception e)
{
websiteLoading.Error(e.GetType().ToString(), e.Message);
Analytics.TrackEvent("History loading error", new Dictionary<string, string>()
{
{ "Exception", e.GetType().ToString() },
{ "Message", e.Message }
});
}
});
}
private async void WebsiteMore_Clicked()
{
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
for (int k = 25 * webPage++; k < 25 * webPage && k < SecretsVault.History.Count; k++)
websiteList.Add(new VideoCard(HistorySet.Items[k].Id, "HL"));
if (websiteList.Count >= SecretsVault.History.Count)
websiteMore.Visibility = Visibility.Collapsed;
else
websiteMore.Complete();
});
}
public void Delete (VideoCard item)
{
list.DeleteItem(item);
} }
} }
} }
+6 -37
View File
@@ -59,7 +59,11 @@
</Grid> </Grid>
</Flyout> </Flyout>
</Button.Flyout> </Button.Flyout>
<PersonPicture Width="25"/> <Ellipse Width="25" Height="25">
<Ellipse.Fill>
<ImageBrush ImageSource="/Assets/Icons/profile.png"/>
</Ellipse.Fill>
</Ellipse>
</Button> </Button>
</StackPanel> </StackPanel>
<CommandBar HorizontalAlignment="Right" Background="Transparent" OverflowButtonVisibility="Collapsed" Visibility="Collapsed" Margin="100,0"> <CommandBar HorizontalAlignment="Right" Background="Transparent" OverflowButtonVisibility="Collapsed" Visibility="Collapsed" Margin="100,0">
@@ -120,47 +124,12 @@
</TransitionCollection> </TransitionCollection>
</ui:NavigationViewList.ItemContainerTransitions> </ui:NavigationViewList.ItemContainerTransitions>
<ui:NavigationViewItem Name="openWeb" Tapped="Web_Tapped" Icon="Globe" Content="Browser" Visibility="Collapsed"/> <ui:NavigationViewItem Name="openWeb" Tapped="Web_Tapped" Icon="Globe" Content="Browser" Visibility="Collapsed"/>
<!--<ui:NavigationViewItem x:Uid="/Main/feedback" Name="feedback" Content="Give a feedback" Tapped="Feedback_Click" Visibility="Collapsed">
<ui:NavigationViewItem.Icon>
<FontIcon Glyph="&#xED15;"/>
</ui:NavigationViewItem.Icon>
</ui:NavigationViewItem>-->
<ui:NavigationViewItem x:Uid="/Main/adsFree" Content="Remove ads" Visibility="Collapsed" Tapped="RemoveAds_Tapped" Name="removeAds"> <ui:NavigationViewItem Content="Remove ads" Visibility="Collapsed" Tapped="RemoveAds_Tapped" Name="removeAds">
<ui:NavigationViewItem.Icon> <ui:NavigationViewItem.Icon>
<FontIcon Glyph="&#xE14D;"/> <FontIcon Glyph="&#xE14D;"/>
</ui:NavigationViewItem.Icon> </ui:NavigationViewItem.Icon>
</ui:NavigationViewItem> </ui:NavigationViewItem>
<!--<ui:NavigationViewItem Name="account" x:Uid="/Main/signIn" Content="Add account" Tapped="SignIn_Click" Visibility="Collapsed">
<ui:NavigationViewItem.Icon>
<FontIcon Glyph="&#xE8FA;"/>
</ui:NavigationViewItem.Icon>
</ui:NavigationViewItem>
<ui:NavigationViewItem Visibility="Collapsed" Name="avatar" Tapped="OpenContext" Padding="-5">
<StackPanel Orientation="Horizontal" Padding="5">
<PersonPicture Height="20" Margin="-5,0,15,0"/>
<TextBlock Name="myName" Text="My account"/>
</StackPanel>
<ui:NavigationViewItem.ContextFlyout>
<Flyout>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<PersonPicture Width="50" Name="avatarFlyout" VerticalAlignment="Top"/>
<StackPanel Grid.Column="1" Margin="5">
<TextBlock Name="myNameFlyout"/>
<TextBlock Style="{StaticResource CaptionTextBlockStyle}" Name="myEmail"/>
<HyperlinkButton x:Uid="/Main/signOut" Content="Log out" Click="Logout_Click"/>
</StackPanel>
</Grid>
</Flyout>
</ui:NavigationViewItem.ContextFlyout>
</ui:NavigationViewItem>-->
</ui:NavigationViewList> </ui:NavigationViewList>
</ui:NavigationView.PaneFooter> </ui:NavigationView.PaneFooter>
+35 -45
View File
@@ -17,6 +17,8 @@ using Windows.UI.Popups;
using Windows.Networking.Connectivity; using Windows.Networking.Connectivity;
using Windows.ApplicationModel.Resources; using Windows.ApplicationModel.Resources;
using Microsoft.Services.Store.Engagement; using Microsoft.Services.Store.Engagement;
using Windows.UI.Xaml.Shapes;
using Windows.UI.Xaml.Media;
namespace FoxTube namespace FoxTube
{ {
@@ -28,7 +30,7 @@ namespace FoxTube
bool wasInvoked = false; bool wasInvoked = false;
public static event ObjectEventHandler VideoPageSizeChanged; public static event ObjectEventHandler VideoPageSizeChanged;
readonly ResourceLoader resources = ResourceLoader.GetForCurrentView("Main"); readonly ResourceLoader resources = ResourceLoader.GetForCurrentView("Main");
Dictionary<Type, Action> headers; Dictionary<Type, string> headers;
public Page PageContent => content.Content as Page; public Page PageContent => content.Content as Page;
@@ -45,6 +47,7 @@ namespace FoxTube
SecretsVault.Purchased += async (sender, e) => SecretsVault.Purchased += async (sender, e) =>
{ {
removeAds.Visibility = (e[0] as bool?).Value ? Visibility.Collapsed : Visibility.Visible; removeAds.Visibility = (e[0] as bool?).Value ? Visibility.Collapsed : Visibility.Visible;
removeAds.Content = $"{resources.GetString("/Main/adsFree/Content")} ({e[1]})";
if (!(bool)e[0]) if (!(bool)e[0])
return; return;
@@ -57,22 +60,16 @@ namespace FoxTube
}; };
SecretsVault.Initialize(); SecretsVault.Initialize();
headers = new Dictionary<Type, Action>() headers = new Dictionary<Type, string>()
{ {
{ typeof(Settings), () => Title.Text = resources.GetString("/Main/settings/Content") }, { typeof(Settings), resources.GetString("/Main/settings/Content") },
{ typeof(ChannelPage), () => Title.Text = resources.GetString("/Main/channel") }, { typeof(ChannelPage), resources.GetString("/Main/channel") },
{ typeof(PlaylistPage), () => Title.Text = resources.GetString("/Main/playlist") }, { typeof(PlaylistPage), resources.GetString("/Main/playlist") },
{ typeof(Search), () => Title.Text = resources.GetString("/Main/searchPlaceholder/PlaceholderText") }, { typeof(Search), resources.GetString("/Main/searchPlaceholder/PlaceholderText") },
{ typeof(Subscriptions), () => Title.Text = resources.GetString("/Main/subscriptions/Content") }, { typeof(Subscriptions), resources.GetString("/Main/subscriptions/Content") },
{ typeof(History), () => { typeof(History), resources.GetString("/Main/history/Content") },
{ { typeof(Home), resources.GetString("/Main/home/Content") },
if((content.Content as History).id == "HL") { typeof(Downloads), resources.GetString("/Main/downloads/Content") }
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") }
}; };
if(StoreServicesFeedbackLauncher.IsSupported()) if(StoreServicesFeedbackLauncher.IsSupported())
@@ -182,7 +179,7 @@ namespace FoxTube
myNameFlyout.Text = SecretsVault.UserInfo.Name; myNameFlyout.Text = SecretsVault.UserInfo.Name;
myEmail.Text = SecretsVault.UserInfo.Email; myEmail.Text = SecretsVault.UserInfo.Email;
avatarFlyout.ProfilePicture = new BitmapImage(SecretsVault.UserInfo.Picture.ToUri()) { DecodePixelHeight = 65, DecodePixelWidth = 65 }; 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; avatar.Visibility = Visibility.Visible;
@@ -200,6 +197,7 @@ namespace FoxTube
for (int k = 0; k < SecretsVault.Subscriptions.Count && k < 10; k++) for (int k = 0; k < SecretsVault.Subscriptions.Count && k < 10; k++)
nav.MenuItems.Add(SecretsVault.Subscriptions[k]); nav.MenuItems.Add(SecretsVault.Subscriptions[k]);
} }
HistorySet.Load();
break; break;
case false: case false:
@@ -283,7 +281,7 @@ namespace FoxTube
content.Navigate(typeof(Home)); 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) if (SettingsStorage.CheckConnection)
try try
@@ -342,7 +340,7 @@ namespace FoxTube
nav.IsPaneOpen = false; nav.IsPaneOpen = false;
VideoPageSizeChanged?.Invoke(this, true); 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"); Title.Text = resources.GetString("/Main/video");
} }
@@ -357,6 +355,11 @@ namespace FoxTube
content.Navigate(typeof(PlaylistPage), id); content.Navigate(typeof(PlaylistPage), id);
} }
public void GoToHistory()
{
content.Navigate(typeof(History));
}
public void GoToDownloads() public void GoToDownloads()
{ {
content.Navigate(typeof(Downloads)); content.Navigate(typeof(Downloads));
@@ -372,8 +375,7 @@ namespace FoxTube
else else
(videoPlaceholder.Content as VideoPage).Player.Minimize(); (videoPlaceholder.Content as VideoPage).Player.Minimize();
try { headers[content.SourcePageType](); } Title.Text = headers[content.SourcePageType];
catch { }
} }
public void MinimizeVideo() public void MinimizeVideo()
@@ -393,8 +395,7 @@ namespace FoxTube
SetNavigationMenu(); SetNavigationMenu();
try { headers[content.SourcePageType](); } Title.Text = headers[content.SourcePageType];
catch { }
} }
void SetNavigationMenu() void SetNavigationMenu()
@@ -445,8 +446,7 @@ namespace FoxTube
SetNavigationMenu(); SetNavigationMenu();
try { headers[content.SourcePageType](); } Title.Text = headers[content.SourcePageType];
catch { }
} }
private void Search_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args) private void Search_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)
@@ -455,9 +455,10 @@ namespace FoxTube
GoToSearch(new SearchParameters(search.Text)); GoToSearch(new SearchParameters(search.Text));
} }
private void Search_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args) private async void Search_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
{
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{ {
//TODO: Make it run async
if (search.Text.Length > 2 && args.Reason == AutoSuggestionBoxTextChangeReason.UserInput) if (search.Text.Length > 2 && args.Reason == AutoSuggestionBoxTextChangeReason.UserInput)
{ {
try try
@@ -474,6 +475,7 @@ namespace FoxTube
} }
catch { search.ItemsSource = new List<string>(); } catch { search.ItemsSource = new List<string>(); }
} }
});
} }
void SetNavigationItem(object item) void SetNavigationItem(object item)
@@ -490,8 +492,7 @@ namespace FoxTube
public void Content_Navigated(object sender, NavigationEventArgs e) public void Content_Navigated(object sender, NavigationEventArgs e)
{ {
try { headers[e.SourcePageType](); } Title.Text = headers[content.SourcePageType];
catch { }
if (!wasInvoked) if (!wasInvoked)
{ {
@@ -517,29 +518,18 @@ namespace FoxTube
SetNavigationItem(null); SetNavigationItem(null);
} }
else if(e.SourcePageType == typeof(History)) else if(e.SourcePageType == typeof(History))
{
if (e.Parameter.ToString() == "HL")
SetNavigationItem(toHistory); SetNavigationItem(toHistory);
else
SetNavigationItem(toLater);
}
else if(e.SourcePageType == typeof(PlaylistPage)) else if(e.SourcePageType == typeof(PlaylistPage))
{ {
if (e.Parameter.ToString() == SecretsVault.UserChannel.ContentDetails.RelatedPlaylists.Likes) if (e.Parameter.ToString() == SecretsVault.UserChannel.ContentDetails.RelatedPlaylists.Likes)
SetNavigationItem(toLiked); SetNavigationItem(toLiked);
else if (e.Parameter.Equals("WL"))
SetNavigationItem(toLater);
} }
} }
else else
wasInvoked = false; 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) if (content.CanGoBack)
nav.IsBackEnabled = true; nav.IsBackEnabled = true;
else else
@@ -580,11 +570,11 @@ namespace FoxTube
if (args.SelectedItem == toHome) if (args.SelectedItem == toHome)
content.Navigate(typeof(Home)); content.Navigate(typeof(Home));
else if (args.SelectedItem == toHistory) else if (args.SelectedItem == toHistory)
content.Navigate(typeof(History), "HL"); content.Navigate(typeof(History));
else if (args.SelectedItem == toLiked) else if (args.SelectedItem == toLiked)
content.Navigate(typeof(PlaylistPage), SecretsVault.UserChannel.ContentDetails.RelatedPlaylists.Likes); content.Navigate(typeof(PlaylistPage), SecretsVault.UserChannel.ContentDetails.RelatedPlaylists.Likes);
else if (args.SelectedItem == toLater) else if (args.SelectedItem == toLater)
content.Navigate(typeof(History), "WL"); content.Navigate(typeof(PlaylistPage), "WL");
else if (args.SelectedItem == toSubscriptions) else if (args.SelectedItem == toSubscriptions)
content.Navigate(typeof(Subscriptions)); content.Navigate(typeof(Subscriptions));
else if (args.SelectedItem == toDownloads) 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) 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) if ((videoPlaceholder.Content as VideoPage).LoadingPage.State != LoadingState.Loaded)
CloseVideo(); CloseVideo();
+13
View File
@@ -58,6 +58,19 @@
</StackPanel> </StackPanel>
<StackPanel Grid.Column="1" Grid.Row="1"> <StackPanel Grid.Column="1" Grid.Row="1">
<Grid Name="wlAlert" Margin="10" BorderBrush="Red" BorderThickness="5" CornerRadius="10" Padding="10" Visibility="Collapsed">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<FontIcon Glyph="&#xF58F;" FontSize="50" VerticalAlignment="Center"/>
<StackPanel Grid.Column="1" Margin="10,0">
<TextBlock x:Uid="/Playlist/wlHeader" FontWeight="Bold" FontSize="25" Text="Don't know how to delete video from 'Watch later' playlist?" VerticalAlignment="Center"/>
<TextBlock x:Uid="/Playlist/wlBody" Text="Unfortunately, for now we are unable to delete videos from 'Watch later' playlist. If you want to do that you have to visit YouTube web page" TextWrapping="WrapWholeWords"/>
</StackPanel>
</Grid>
<local:VideoGrid x:Name="list"/> <local:VideoGrid x:Name="list"/>
<controls:ShowMore x:Name="more" Clicked="ShowMore_Clicked"/> <controls:ShowMore x:Name="more" Clicked="ShowMore_Clicked"/>
</StackPanel> </StackPanel>
+63
View File
@@ -4,6 +4,7 @@ using Google.Apis.YouTube.v3.Data;
using Microsoft.AppCenter.Analytics; using Microsoft.AppCenter.Analytics;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Windows.ApplicationModel.DataTransfer; using Windows.ApplicationModel.DataTransfer;
using Windows.ApplicationModel.Resources; using Windows.ApplicationModel.Resources;
using Windows.Foundation; using Windows.Foundation;
@@ -26,6 +27,7 @@ namespace FoxTube.Pages
PlaylistItemsResource.ListRequest request; PlaylistItemsResource.ListRequest request;
string token; string token;
int page = 1;
public PlaylistPage() public PlaylistPage()
{ {
@@ -40,6 +42,8 @@ namespace FoxTube.Pages
Parameter = e.Parameter; Parameter = e.Parameter;
if (e.Parameter == null) if (e.Parameter == null)
loading.Error("NullReferenceException", "Unable to initialize page. Playlist ID is not stated."); loading.Error("NullReferenceException", "Unable to initialize page. Playlist ID is not stated.");
else if (e.Parameter as string == "WL")
LoadWL();
else else
Initialize(e.Parameter as string); 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<string, string>()
{
{ "Exception", e.GetType().ToString() },
{ "Message", e.Message }
});
}
}
private void toChannel_Click(object sender, RoutedEventArgs e) private void toChannel_Click(object sender, RoutedEventArgs e)
{ {
Methods.MainPage.GoToChannel(item.Snippet.ChannelId); Methods.MainPage.GoToChannel(item.Snippet.ChannelId);
@@ -134,6 +180,12 @@ namespace FoxTube.Pages
private async void ShowMore_Clicked() private async void ShowMore_Clicked()
{ {
if(playlistId == "WL")
{
MoreWL();
return;
}
request.PageToken = token; request.PageToken = token;
PlaylistItemListResponse response = await request.ExecuteAsync(); PlaylistItemListResponse response = await request.ExecuteAsync();
@@ -149,6 +201,17 @@ namespace FoxTube.Pages
list.Add(new VideoCard(i.ContentDetails.VideoId, playlistId)); 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) public void DeleteItem(FrameworkElement card)
{ {
list.DeleteItem(card); list.DeleteItem(card);
+1
View File
@@ -29,6 +29,7 @@
<TextBlock x:Uid="/General/playback" Text="Playback" FontSize="22" Margin="0,10,0,0"/> <TextBlock x:Uid="/General/playback" Text="Playback" FontSize="22" Margin="0,10,0,0"/>
<ComboBox x:Uid="/General/quality" Width="250" Header="Default video playback quality" Name="quality" SelectionChanged="quality_SelectionChanged"> <ComboBox x:Uid="/General/quality" Width="250" Header="Default video playback quality" Name="quality" SelectionChanged="quality_SelectionChanged">
<ComboBoxItem Tag="remember" x:Uid="/General/remember" Content="Remember my choice"/> <ComboBoxItem Tag="remember" x:Uid="/General/remember" Content="Remember my choice"/>
<ComboBoxItem Tag="auto" x:Uid="/General/auto" Content="Auto"/>
</ComboBox> </ComboBox>
<ToggleSwitch x:Uid="/General/metered" OnContent="Notify when playing on metered connection" OffContent="Notify when playing on metered connection" Name="mobileWarning" Toggled="mobileWarning_Toggled"/> <ToggleSwitch x:Uid="/General/metered" OnContent="Notify when playing on metered connection" OffContent="Notify when playing on metered connection" Name="mobileWarning" Toggled="mobileWarning_Toggled"/>
<ToggleSwitch x:Uid="/General/autoplay" OnContent="Play videos automatically" OffContent="Play videos automatically" Name="autoplay" Toggled="autoplay_Toggled"/> <ToggleSwitch x:Uid="/General/autoplay" OnContent="Play videos automatically" OffContent="Play videos automatically" Name="autoplay" Toggled="autoplay_Toggled"/>
+1
View File
@@ -58,6 +58,7 @@
<local:VideoPlayer x:Name="player" NextClicked="Player_NextClicked" MiniMode="Player_Minimize"/> <local:VideoPlayer x:Name="player" NextClicked="Player_NextClicked" MiniMode="Player_Minimize"/>
<PivotItem Header="Description" Name="descriptionPanel"> <PivotItem Header="Description" Name="descriptionPanel">
<StackPanel Margin="0,10"> <StackPanel Margin="0,10">
<Button FontFamily="Segoe UI, Segoe MDL2 Assets" Content="&#xE122; Continue from: 00:10:37" Name="left" Click="Left_Click" Visibility="Collapsed"/>
<TextBlock IsTextSelectionEnabled="True" Name="title" Text="[Video title]" FontSize="25" TextWrapping="WrapWholeWords" HorizontalTextAlignment="Start"/> <TextBlock IsTextSelectionEnabled="True" Name="title" Text="[Video title]" FontSize="25" TextWrapping="WrapWholeWords" HorizontalTextAlignment="Start"/>
<TextBlock Text="Published at: " Name="date"/> <TextBlock Text="Published at: " Name="date"/>
<Grid> <Grid>
+78 -11
View File
@@ -49,6 +49,8 @@ namespace FoxTube.Pages
public string videoId; public string videoId;
public string playlistId = null; public string playlistId = null;
public Video item; public Video item;
public HistoryItem history;
public bool incognito = false;
bool isExtended = false; bool isExtended = false;
@@ -92,27 +94,28 @@ namespace FoxTube.Pages
protected override void OnNavigatedTo(NavigationEventArgs e) protected override void OnNavigatedTo(NavigationEventArgs e)
{ {
base.OnNavigatedTo(e); base.OnNavigatedTo(e);
if (e.Parameter == null || string.IsNullOrWhiteSpace((e.Parameter as string[])[0])) if (e.Parameter == null)
loading.Error("NullReferenceException", "Unable to initialize page. Video ID is not stated."); loading.Error("NullReferenceException", "Unable to initialize page. Video ID is not stated.");
else else
Initialize(e.Parameter as string[]); Initialize(e.Parameter as object[]);
} }
public async void Initialize(string[] ids) public async void Initialize(object[] ids)
{ {
loading.Refresh(); loading.Refresh();
try try
{ {
videoId = ids[0]; videoId = ids[0] as string;
incognito = (bool)ids[2];
if (ids[1] != null) if (ids[1] != null)
LoadPlaylist(ids[1]); LoadPlaylist(ids[1] as string);
else else
pivot.Items.Remove(playlist); pivot.Items.Remove(playlist);
VideosResource.ListRequest request = SecretsVault.Service.Videos.List("snippet,statistics,status,contentDetails,liveStreamingDetails"); VideosResource.ListRequest request = SecretsVault.Service.Videos.List("snippet,statistics,status,contentDetails,liveStreamingDetails");
request.Id = ids[0]; request.Id = ids[0] as string;
request.Hl = SettingsStorage.RelevanceLanguage; request.Hl = SettingsStorage.RelevanceLanguage;
item = (await request.ExecuteAsync()).Items[0]; item = (await request.ExecuteAsync()).Items[0];
@@ -181,6 +184,31 @@ namespace FoxTube.Pages
List<VideoPlaylistItem> items = new List<VideoPlaylistItem>(); List<VideoPlaylistItem> items = new List<VideoPlaylistItem>();
VideoPlaylistItem selection = null; VideoPlaylistItem selection = null;
if (id == "WL")
{
SecretsVault.WatchLater = await Methods.GetLater();
foreach (string i in SecretsVault.WatchLater)
{
VideosResource.ListRequest request = SecretsVault.Service.Videos.List("snippet");
request.Id = i;
Video v = (await request.ExecuteAsync()).Items[0];
items.Add(new VideoPlaylistItem(v.Snippet.Thumbnails.Medium.Url, v.Snippet.Title, i));
}
selection = items.Find(i => i.Id == item.Id);
for (int k = 0; k < items.Count; k++)
items[k].Number = k + 1;
playlistName.Text = resources.GetString("/Main/later/Content");
playlistChannel.Text = SecretsVault.UserChannel.Snippet.Title;
playlistCounter.Text = $"{items.IndexOf(selection) + 1}/{SecretsVault.WatchLater.Count}";
}
else
{
//Retrieving data //Retrieving data
PlaylistsResource.ListRequest playlistRequest = SecretsVault.Service.Playlists.List("snippet,contentDetails"); PlaylistsResource.ListRequest playlistRequest = SecretsVault.Service.Playlists.List("snippet,contentDetails");
playlistRequest.Id = id; playlistRequest.Id = id;
@@ -212,6 +240,7 @@ namespace FoxTube.Pages
playlistChannel.Text = playlistItem.Snippet.ChannelTitle; playlistChannel.Text = playlistItem.Snippet.ChannelTitle;
playlistCounter.Text = $"{items.IndexOf(selection) + 1}/{playlistItem.ContentDetails.ItemCount}"; playlistCounter.Text = $"{items.IndexOf(selection) + 1}/{playlistItem.ContentDetails.ItemCount}";
}
playlistList.ItemsSource = items; playlistList.ItemsSource = items;
playlistList.SelectedItem = selection; playlistList.SelectedItem = selection;
@@ -279,8 +308,15 @@ namespace FoxTube.Pages
subscribe.Visibility = Visibility.Collapsed; subscribe.Visibility = Visibility.Collapsed;
} }
if(HistorySet.Items.Exists(i => i.Id == videoId) && HistorySet.Items.Find(i => i.Id == videoId).LeftOn.TotalSeconds >= 30 && Methods.GetDuration(item.ContentDetails.Duration).TotalSeconds - HistorySet.Items.Find(i => i.Id == videoId).LeftOn.TotalSeconds >= 30)
{
history = HistorySet.Items.Find(i => i.Id == videoId);
left.Visibility = Visibility.Visible;
left.Content = $"\xE122 {resources.GetString("/VideoPage/continue")}: {history.LeftOn.ToString(@"hh\:mm\:ss")}";
}
//Initializing player //Initializing player
player.Initialize(item, item1.Snippet.Thumbnails.Medium.Url); player.Initialize(item, item1.Snippet.Thumbnails.Medium.Url, incognito);
LoadRelatedVideos(); LoadRelatedVideos();
} }
@@ -408,8 +444,8 @@ namespace FoxTube.Pages
private async void openBrowser_Click(object sender, RoutedEventArgs e) private async void openBrowser_Click(object sender, RoutedEventArgs e)
{ {
player.Pause(); player.Pause();
string timecode = player.Position.TotalSeconds > 10 ? string timecode = player.Player.Position.TotalSeconds > 10 ?
"&t=" + (int)player.Position.TotalSeconds + "s" : string.Empty; "&t=" + (int)player.Player.Position.TotalSeconds + "s" : string.Empty;
await Launcher.LaunchUriAsync($"https://www.youtube.com/watch?v={videoId}{timecode}".ToUri()); await Launcher.LaunchUriAsync($"https://www.youtube.com/watch?v={videoId}{timecode}".ToUri());
} }
@@ -657,9 +693,35 @@ namespace FoxTube.Pages
await playlistDialog.ShowAsync(); await playlistDialog.ShowAsync();
} }
private void Wl_Click(object sender, RoutedEventArgs e) private async void Wl_Click(object sender, RoutedEventArgs e)
{ {
// TODO: Add video to WL playlist if (wl.IsChecked)
{
try
{
PlaylistItem playlist = new PlaylistItem
{
Snippet = new PlaylistItemSnippet
{
PlaylistId = "WL",
ResourceId = new ResourceId
{
VideoId = item.Id,
Kind = "youtube#video"
}
}
};
PlaylistItemsResource.InsertRequest request = SecretsVault.Service.PlaylistItems.Insert(playlist, "snippet");
await request.ExecuteAsync();
}
catch
{
wl.IsChecked = false;
}
}
else
wl.IsChecked = true;
} }
private async void ContentDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args) private async void ContentDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
@@ -713,5 +775,10 @@ namespace FoxTube.Pages
Item_Click(menuItem, null); Item_Click(menuItem, null);
} }
private void Left_Click(object sender, RoutedEventArgs e)
{
Player.Player.Position = history.LeftOn;
}
} }
} }
+3
View File
@@ -195,4 +195,7 @@
<data name="downloads.Text" xml:space="preserve"> <data name="downloads.Text" xml:space="preserve">
<value>Downloads</value> <value>Downloads</value>
</data> </data>
<data name="sponsored.Text" xml:space="preserve">
<value>Sponsored content</value>
</data>
</root> </root>
+1 -1
View File
@@ -141,7 +141,7 @@
<data name="editorDelete.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve"> <data name="editorDelete.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
<value>Delete comment</value> <value>Delete comment</value>
</data> </data>
<data name="editorSubmin.Content" xml:space="preserve"> <data name="editorSubmit.Content" xml:space="preserve">
<value>Submit</value> <value>Submit</value>
</data> </data>
<data name="failedDelete" xml:space="preserve"> <data name="failedDelete" xml:space="preserve">
+3
View File
@@ -207,4 +207,7 @@
<data name="relevanceLanguage.Header" xml:space="preserve"> <data name="relevanceLanguage.Header" xml:space="preserve">
<value>Search relevance language</value> <value>Search relevance language</value>
</data> </data>
<data name="auto.Content" xml:space="preserve">
<value>Auto</value>
</data>
</root> </root>
+33
View File
@@ -120,6 +120,27 @@
<data name="addTo.Label" xml:space="preserve"> <data name="addTo.Label" xml:space="preserve">
<value>Add to</value> <value>Add to</value>
</data> </data>
<data name="appHistory.Header" xml:space="preserve">
<value>Application</value>
</data>
<data name="clear.Content" xml:space="preserve">
<value> Clear history</value>
</data>
<data name="historyClear.Body" xml:space="preserve">
<value>Are you sure? This action cannot be undone</value>
</data>
<data name="historyClear.Header" xml:space="preserve">
<value>Delete app history</value>
</data>
<data name="hlBody.Text" xml:space="preserve">
<value>Unfortunately, for now we are unable to manage your youtube history. If you want to see your web history go to Website tab. If you want to delete some items, please go to website</value>
</data>
<data name="hlHeader.Text" xml:space="preserve">
<value>Missing some stuff?</value>
</data>
<data name="no" xml:space="preserve">
<value>No</value>
</data>
<data name="openWeb.Label" xml:space="preserve"> <data name="openWeb.Label" xml:space="preserve">
<value>Open in browser</value> <value>Open in browser</value>
</data> </data>
@@ -132,4 +153,16 @@
<data name="videos" xml:space="preserve"> <data name="videos" xml:space="preserve">
<value>videos</value> <value>videos</value>
</data> </data>
<data name="webHistory.Header" xml:space="preserve">
<value>Website</value>
</data>
<data name="wlBody.Text" xml:space="preserve">
<value>Unfortunately, for now we are unable to delete videos from 'Watch later' playlist. If you want to do that you have to visit YouTube web page</value>
</data>
<data name="wlHeader.Text" xml:space="preserve">
<value>Don't know how to delete video from 'Watch later' playlist?</value>
</data>
<data name="yes" xml:space="preserve">
<value>Yes</value>
</data>
</root> </root>
+6
View File
@@ -297,4 +297,10 @@
<data name="live.Text" xml:space="preserve"> <data name="live.Text" xml:space="preserve">
<value>🔴 LIVE</value> <value>🔴 LIVE</value>
</data> </data>
<data name="continue" xml:space="preserve">
<value>Continue from</value>
</data>
<data name="auto" xml:space="preserve">
<value>Auto</value>
</data>
</root> </root>
+3
View File
@@ -195,4 +195,7 @@
<data name="downloads.Text" xml:space="preserve"> <data name="downloads.Text" xml:space="preserve">
<value>Загрузки</value> <value>Загрузки</value>
</data> </data>
<data name="sponsored.Text" xml:space="preserve">
<value>Реклама</value>
</data>
</root> </root>
+1 -1
View File
@@ -141,7 +141,7 @@
<data name="editorDelete.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve"> <data name="editorDelete.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
<value>Удалить комментарий</value> <value>Удалить комментарий</value>
</data> </data>
<data name="editorSubmin.Content" xml:space="preserve"> <data name="editorSubmit.Content" xml:space="preserve">
<value>Отправить</value> <value>Отправить</value>
</data> </data>
<data name="failedDelete" xml:space="preserve"> <data name="failedDelete" xml:space="preserve">
+3
View File
@@ -207,4 +207,7 @@
<data name="relevanceLanguage.Header" xml:space="preserve"> <data name="relevanceLanguage.Header" xml:space="preserve">
<value>Предпочитаемый язык поиска</value> <value>Предпочитаемый язык поиска</value>
</data> </data>
<data name="auto.Content" xml:space="preserve">
<value>Авто</value>
</data>
</root> </root>
+33
View File
@@ -120,6 +120,27 @@
<data name="addTo.Label" xml:space="preserve"> <data name="addTo.Label" xml:space="preserve">
<value>Добавить в</value> <value>Добавить в</value>
</data> </data>
<data name="appHistory.Header" xml:space="preserve">
<value>Приложение</value>
</data>
<data name="clear.Content" xml:space="preserve">
<value> Очистить историю</value>
</data>
<data name="historyClear.Body" xml:space="preserve">
<value>Вы уверены? Это действие невозможно отменить</value>
</data>
<data name="historyClear.Header" xml:space="preserve">
<value>Очистить историю просмотра?</value>
</data>
<data name="hlBody.Text" xml:space="preserve">
<value>К сожалению, пока мы не можем управлять историей просмотров. Если вам нужна история просмотров сайта, перейдите на вкладку "Сайт". Если вы хотите что-то удалить из истории, пожалуйста перейдите на веб-версию сайта</value>
</data>
<data name="hlHeader.Text" xml:space="preserve">
<value>Не можете что-то найти?</value>
</data>
<data name="no" xml:space="preserve">
<value>Нет</value>
</data>
<data name="openWeb.Label" xml:space="preserve"> <data name="openWeb.Label" xml:space="preserve">
<value>Открыть в браузере</value> <value>Открыть в браузере</value>
</data> </data>
@@ -132,4 +153,16 @@
<data name="videos" xml:space="preserve"> <data name="videos" xml:space="preserve">
<value>видео</value> <value>видео</value>
</data> </data>
<data name="webHistory.Header" xml:space="preserve">
<value>Сайт</value>
</data>
<data name="wlBody.Text" xml:space="preserve">
<value>К сожалению, пока мы не можем удалять видео из этого плейлиста. Если вы хотите сделать это, вам придется перейти на веб-версию сайта</value>
</data>
<data name="wlHeader.Text" xml:space="preserve">
<value>Не знаете как удалить видео из "Посмотреть позже"?</value>
</data>
<data name="yes" xml:space="preserve">
<value>Да</value>
</data>
</root> </root>
+6
View File
@@ -297,4 +297,10 @@
<data name="live.Text" xml:space="preserve"> <data name="live.Text" xml:space="preserve">
<value>🔴 ПРЯМОЙ ЭФИР</value> <value>🔴 ПРЯМОЙ ЭФИР</value>
</data> </data>
<data name="continue" xml:space="preserve">
<value>Продолжить с</value>
</data>
<data name="auto" xml:space="preserve">
<value>Авто</value>
</data>
</root> </root>
+91 -380
View File
@@ -6,9 +6,7 @@
xmlns:adverts="using:FoxTube.Controls.Adverts"> xmlns:adverts="using:FoxTube.Controls.Adverts">
<Style TargetType="local:PlayerControls"> <Style TargetType="local:PlayerControls">
<Setter Property="IsTabStop" Value="False" />
<Setter Property="Background" Value="Transparent" /> <Setter Property="Background" Value="Transparent" />
<Setter Property="FlowDirection" Value="LeftToRight" />
<Setter Property="UseSystemFocusVisuals" Value="True" /> <Setter Property="UseSystemFocusVisuals" Value="True" />
<Setter Property="IsTextScaleFactorEnabled" Value="False" /> <Setter Property="IsTextScaleFactorEnabled" Value="False" />
<Setter Property="Template"> <Setter Property="Template">
@@ -20,69 +18,24 @@
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.Resources> <Grid.Resources>
<Style x:Key="AppBarButtonStyle" TargetType="AppBarButton">
<Setter Property="Width" Value="{ThemeResource MTCMediaButtonWidth}" />
<Setter Property="Height" Value="{ThemeResource MTCMediaButtonHeight}" />
<Setter Property="AllowFocusOnInteraction" Value="True" />
</Style>
<Style x:Key="CommandBarStyle" TargetType="CommandBar">
<Setter Property="Height" Value="{ThemeResource MTCMediaButtonHeight}" />
<Setter Property="Background" Value="Transparent" />
</Style>
<Style x:Key="MediaTextBlockStyle" TargetType="TextBlock">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Foreground" Value="{ThemeResource SystemControlForegroundBaseHighBrush}" />
<Setter Property="FontSize" Value="{ThemeResource MTCMediaFontSize}" />
<Setter Property="FontFamily" Value="{ThemeResource MTCMediaFontFamily}" />
<Setter Property="Style" Value="{ThemeResource CaptionTextBlockStyle }" />
<Setter Property="IsTextScaleFactorEnabled" Value="False" />
</Style>
<Style x:Key="PlayerSeek" TargetType="Slider"> <Style x:Key="PlayerSeek" TargetType="Slider">
<Setter Property="Background" Value="{ThemeResource SliderTrackFill}" /> <Setter Property="Margin" Value="0"/>
<Setter Property="BorderThickness" Value="{ThemeResource SliderBorderThemeThickness}" />
<Setter Property="Foreground" Value="{ThemeResource SliderTrackValueFill}" />
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
<Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
<Setter Property="ManipulationMode" Value="None" />
<Setter Property="UseSystemFocusVisuals" Value="True" />
<Setter Property="IsFocusEngagementEnabled" Value="True" />
<Setter Property="Template"> <Setter Property="Template">
<Setter.Value> <Setter.Value>
<ControlTemplate TargetType="Slider"> <ControlTemplate TargetType="Slider">
<Grid Margin="{TemplateBinding Padding}"> <Grid>
<Grid.Resources> <Grid.Resources>
<Style TargetType="Thumb" x:Key="SliderThumbStyle"> <Style TargetType="Thumb">
<Setter Property="BorderThickness" Value="0" /> <Setter Property="Background" Value="Red" />
<Setter Property="Background" Value="{ThemeResource SliderThumbBackground}" />
<Setter Property="Foreground" Value="{ThemeResource SystemControlBackgroundChromeMediumBrush}" />
<Setter Property="Template"> <Setter Property="Template">
<Setter.Value> <Setter.Value>
<ControlTemplate TargetType="Thumb"> <ControlTemplate TargetType="Thumb">
<Ellipse x:Name="ellipse" <Ellipse Fill="{TemplateBinding Background}"/>
Stroke="{TemplateBinding Background}"
StrokeThickness="2"
Fill="{TemplateBinding Foreground}" />
</ControlTemplate> </ControlTemplate>
</Setter.Value> </Setter.Value>
</Setter> </Setter>
</Style> </Style>
<Style TargetType="ProgressBar" x:Key="MediaSliderProgressBarStyle">
<Setter Property="Height" Value="{ThemeResource SliderTrackThemeHeight}" />
<Setter Property="Minimum" Value="0" />
<Setter Property="Maximum" Value="100" />
<Setter Property="Foreground" Value="{ThemeResource SystemControlHighlightChromeAltLowBrush}" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="Transparent" />
<Setter Property="BorderThickness" Value="1" />
</Style>
</Grid.Resources> </Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<VisualStateManager.VisualStateGroups> <VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates"> <VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/> <VisualState x:Name="Normal"/>
@@ -91,61 +44,25 @@
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalTrackRect" Storyboard.TargetProperty="Fill"> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalTrackRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackFillPressed}" /> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackFillPressed}" />
</ObjectAnimationUsingKeyFrames> </ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalTrackRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackFillPressed}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalThumb" Storyboard.TargetProperty="Background"> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalThumb" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderThumbBackgroundPressed}" /> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderThumbBackgroundPressed}" />
</ObjectAnimationUsingKeyFrames> </ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalThumb" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderThumbBackgroundPressed}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SliderContainer" Storyboard.TargetProperty="Background"> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="SliderContainer" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderContainerBackgroundPressed}" /> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderContainerBackgroundPressed}" />
</ObjectAnimationUsingKeyFrames> </ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalDecreaseRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackValueFillPressed}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalDecreaseRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackValueFillPressed}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard> </Storyboard>
</VisualState> </VisualState>
<VisualState x:Name="Disabled"> <VisualState x:Name="Disabled">
<Storyboard> <Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HeaderContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderHeaderForegroundDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalDecreaseRect" Storyboard.TargetProperty="Fill"> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalDecreaseRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackValueFillDisabled}" /> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackValueFillDisabled}" />
</ObjectAnimationUsingKeyFrames> </ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalTrackRect" Storyboard.TargetProperty="Fill"> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalTrackRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackFillDisabled}" /> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackFillDisabled}" />
</ObjectAnimationUsingKeyFrames> </ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalDecreaseRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackValueFillDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalTrackRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackFillDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalThumb" Storyboard.TargetProperty="Background"> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalThumb" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderThumbBackgroundDisabled}" /> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderThumbBackgroundDisabled}" />
</ObjectAnimationUsingKeyFrames> </ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalThumb" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderThumbBackgroundDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="TopTickBar" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTickBarFillDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="BottomTickBar" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTickBarFillDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="LeftTickBar" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTickBarFillDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="RightTickBar" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTickBarFillDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SliderContainer" Storyboard.TargetProperty="Background"> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="SliderContainer" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderContainerBackgroundDisabled}" /> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderContainerBackgroundDisabled}" />
</ObjectAnimationUsingKeyFrames> </ObjectAnimationUsingKeyFrames>
@@ -156,24 +73,12 @@
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalTrackRect" Storyboard.TargetProperty="Fill"> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalTrackRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackFillPointerOver}" /> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackFillPointerOver}" />
</ObjectAnimationUsingKeyFrames> </ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalTrackRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackFillPointerOver}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalThumb" Storyboard.TargetProperty="Background"> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalThumb" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderThumbBackgroundPointerOver}" /> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderThumbBackgroundPointerOver}" />
</ObjectAnimationUsingKeyFrames> </ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalThumb" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderThumbBackgroundPointerOver}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SliderContainer" Storyboard.TargetProperty="Background"> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="SliderContainer" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderContainerBackgroundPointerOver}" /> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderContainerBackgroundPointerOver}" />
</ObjectAnimationUsingKeyFrames> </ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalDecreaseRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackValueFillPointerOver}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalDecreaseRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackValueFillPointerOver}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard> </Storyboard>
</VisualState> </VisualState>
</VisualStateGroup> </VisualStateGroup>
@@ -189,156 +94,43 @@
</ObjectAnimationUsingKeyFrames> </ObjectAnimationUsingKeyFrames>
</Storyboard> </Storyboard>
</VisualState> </VisualState>
<VisualState x:Name="FocusEngagedVertical">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SliderContainer" Storyboard.TargetProperty="(Control.IsTemplateFocusTarget)">
<DiscreteObjectKeyFrame KeyTime="0" Value="False" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalThumb" Storyboard.TargetProperty="(Control.IsTemplateFocusTarget)">
<DiscreteObjectKeyFrame KeyTime="0" Value="True" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup> </VisualStateGroup>
</VisualStateManager.VisualStateGroups> </VisualStateManager.VisualStateGroups>
<ContentPresenter x:Name="HeaderContentPresenter"
x:DeferLoadStrategy="Lazy" <Grid x:Name="SliderContainer" Control.IsTemplateFocusTarget="True" Background="{ThemeResource SliderContainerBackground}">
Visibility="Collapsed" <Grid x:Name="HorizontalTemplate">
Foreground="{ThemeResource SliderHeaderForeground}"
Margin="{ThemeResource SliderHeaderThemeMargin}"
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
FontWeight="{ThemeResource SliderHeaderThemeFontWeight}"
TextWrapping="Wrap" />
<Grid x:Name="SliderContainer"
Background="{ThemeResource SliderContainerBackground}"
Grid.Row="1"
Control.IsTemplateFocusTarget="True">
<Grid x:Name="HorizontalTemplate" MinHeight="44">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*" /> <ColumnDefinition/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="15" /> <Rectangle x:Name="HorizontalTrackRect" Height="{StaticResource SliderTrackThemeHeight}" Fill="{TemplateBinding Background}" Grid.ColumnSpan="3"/>
<RowDefinition Height="Auto" />
<RowDefinition Height="15" /> <ProgressBar x:Name="DownloadProgressIndicator" Grid.ColumnSpan="3"
</Grid.RowDefinitions>
<Rectangle x:Name="HorizontalTrackRect"
Fill="{TemplateBinding Background}"
Height="{ThemeResource SliderTrackThemeHeight}" Height="{ThemeResource SliderTrackThemeHeight}"
Grid.Row="1" Foreground="{ThemeResource SystemControlHighlightChromeAltLowBrush}"
Grid.ColumnSpan="3" /> Background="Transparent"
<ProgressBar x:Name="DownloadProgressIndicator" BorderBrush="Transparent"
Style="{StaticResource MediaSliderProgressBarStyle}" BorderThickness="1"/>
Grid.Row="1"
Grid.ColumnSpan="3" <Rectangle Height="4" x:Name="HorizontalDecreaseRect" Fill="{StaticResource SystemAccentColor}"/>
HorizontalAlignment="Stretch"
VerticalAlignment="Center" />
<Rectangle x:Name="HorizontalDecreaseRect" Fill="{TemplateBinding Foreground}" Grid.Row="1" />
<TickBar x:Name="TopTickBar"
Visibility="Collapsed"
Fill="{ThemeResource SliderTickBarFill}"
Height="{ThemeResource SliderOutsideTickBarThemeHeight}"
VerticalAlignment="Bottom"
Margin="0,0,0,4"
Grid.ColumnSpan="3" />
<TickBar x:Name="HorizontalInlineTickBar"
Visibility="Collapsed"
Fill="{ThemeResource SliderInlineTickBarFill}"
Height="{ThemeResource SliderTrackThemeHeight}"
Grid.Row="1"
Grid.ColumnSpan="3" />
<TickBar x:Name="BottomTickBar"
Visibility="Collapsed"
Fill="{ThemeResource SliderTickBarFill}"
Height="{ThemeResource SliderOutsideTickBarThemeHeight}"
VerticalAlignment="Top"
Margin="0,4,0,0"
Grid.Row="2"
Grid.ColumnSpan="3" />
<Thumb x:Name="HorizontalThumb" <Thumb x:Name="HorizontalThumb"
Style="{StaticResource SliderThumbStyle}" Height="15" Width="15" Grid.Column="1"
Height="15"
Width="15"
Grid.Row="0"
Grid.RowSpan="3"
Grid.Column="1"
AutomationProperties.AccessibilityView="Raw"> AutomationProperties.AccessibilityView="Raw">
<ToolTipService.ToolTip>
<ToolTip x:Name="ThumbnailTooltip">
<ContentPresenter Content="{Binding}" />
</ToolTip>
</ToolTipService.ToolTip>
<Thumb.DataContext> <Thumb.DataContext>
<Grid Height="112" Width="192"> <Grid>
<Image x:Name="ThumbnailImage"/> <Grid.RowDefinitions>
<Border Background="{ThemeResource SystemControlBackgroundBaseMediumBrush}" <RowDefinition Height="112"/>
VerticalAlignment="Bottom" <RowDefinition Height="Auto"/>
HorizontalAlignment="Left"> </Grid.RowDefinitions>
<TextBlock x:Name="TimeElapsedPreview" <Image x:Name="ThumbnailImage" Width="192"/>
Margin="6,1,6,3" <TextBlock Grid.Row="1" x:Name="TimeElapsedPreview"/>
Style="{StaticResource BodyTextBlockStyle}"
IsTextScaleFactorEnabled="False"
Foreground="{ThemeResource SystemControlPageTextBaseMediumBrush}" />
</Border>
</Grid> </Grid>
</Thumb.DataContext> </Thumb.DataContext>
</Thumb> </Thumb>
</Grid> </Grid>
<Grid x:Name="VerticalTemplate" MinWidth="44" Visibility="Collapsed">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="18" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="18" />
</Grid.ColumnDefinitions>
<Rectangle x:Name="VerticalTrackRect"
Fill="{TemplateBinding Background}"
Width="{ThemeResource SliderTrackThemeHeight}"
Grid.Column="1"
Grid.RowSpan="3" />
<Rectangle x:Name="VerticalDecreaseRect"
Fill="{TemplateBinding Foreground}"
Grid.Column="1"
Grid.Row="2" />
<TickBar x:Name="LeftTickBar"
Visibility="Collapsed"
Fill="{ThemeResource SliderTickBarFill}"
Width="{ThemeResource SliderOutsideTickBarThemeHeight}"
HorizontalAlignment="Right"
Margin="0,0,4,0"
Grid.RowSpan="3" />
<TickBar x:Name="VerticalInlineTickBar"
Visibility="Collapsed"
Fill="{ThemeResource SliderInlineTickBarFill}"
Width="{ThemeResource SliderTrackThemeHeight}"
Grid.Column="1"
Grid.RowSpan="3" />
<TickBar x:Name="RightTickBar"
Visibility="Collapsed"
Fill="{ThemeResource SliderTickBarFill}"
Width="{ThemeResource SliderOutsideTickBarThemeHeight}"
HorizontalAlignment="Left"
Margin="4,0,0,0"
Grid.Column="2"
Grid.RowSpan="3" />
<Thumb x:Name="VerticalThumb"
Style="{StaticResource SliderThumbStyle}"
DataContext="{TemplateBinding Value}"
Width="24"
Height="8"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="3"
FocusVisualMargin="-6,-14,-6,-14"
AutomationProperties.AccessibilityView="Raw" />
</Grid>
</Grid> </Grid>
</Grid> </Grid>
</ControlTemplate> </ControlTemplate>
@@ -348,7 +140,6 @@
</Grid.Resources> </Grid.Resources>
<VisualStateManager.VisualStateGroups> <VisualStateManager.VisualStateGroups>
<!-- ControlPanel Visibility states -->
<VisualStateGroup x:Name="ControlPanelVisibilityStates"> <VisualStateGroup x:Name="ControlPanelVisibilityStates">
<VisualState x:Name="ControlPanelFadeIn"> <VisualState x:Name="ControlPanelFadeIn">
<Storyboard> <Storyboard>
@@ -356,66 +147,34 @@
<EasingDoubleKeyFrame KeyTime="0" Value="0" /> <EasingDoubleKeyFrame KeyTime="0" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="1" /> <EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="1" />
</DoubleAnimationUsingKeyFrames> </DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity" Storyboard.TargetName="center">
<EasingDoubleKeyFrame KeyTime="0" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="1" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity" Storyboard.TargetName="centerBgr">
<EasingDoubleKeyFrame KeyTime="0" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="1" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimation Storyboard.TargetProperty="Y" Storyboard.TargetName="TranslateVerticalBottom" From="50" To ="0" Duration="0:0:0.3"/>
<DoubleAnimation Storyboard.TargetProperty="Y" Storyboard.TargetName="TranslateVerticalTop" From="-50" To ="0" Duration="0:0:0.3"/>
</Storyboard> </Storyboard>
</VisualState> </VisualState>
<VisualState x:Name="ControlPanelFadeOut"> <VisualState x:Name="ControlPanelFadeOut">
<Storyboard> <Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity" Storyboard.TargetName="Border"> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity" Storyboard.TargetName="Border">
<EasingDoubleKeyFrame KeyTime="0" Value="1" /> <EasingDoubleKeyFrame KeyTime="0" Value="1" />
<EasingDoubleKeyFrame KeyTime="0:0:0.7" Value="0" /> <EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="0" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity" Storyboard.TargetName="center">
<EasingDoubleKeyFrame KeyTime="0" Value="1" />
<EasingDoubleKeyFrame KeyTime="0:0:0.7" Value="0" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity" Storyboard.TargetName="centerBgr">
<EasingDoubleKeyFrame KeyTime="0" Value="1" />
<EasingDoubleKeyFrame KeyTime="0:0:0.7" Value="0" />
</DoubleAnimationUsingKeyFrames> </DoubleAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="IsHitTestVisible" Storyboard.TargetName="Border"> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="IsHitTestVisible" Storyboard.TargetName="Border">
<DiscreteObjectKeyFrame KeyTime="0" Value="False" /> <DiscreteObjectKeyFrame KeyTime="0" Value="False" />
</ObjectAnimationUsingKeyFrames> </ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="IsHitTestVisible" Storyboard.TargetName="center">
<DiscreteObjectKeyFrame KeyTime="0" Value="False" />
</ObjectAnimationUsingKeyFrames>
<DoubleAnimation Storyboard.TargetProperty="Y" Storyboard.TargetName="TranslateVerticalBottom" From="0" To ="50" Duration="0:0:0.7"/>
<DoubleAnimation Storyboard.TargetProperty="Y" Storyboard.TargetName="TranslateVerticalTop" From="0" To ="-50" Duration="0:0:0.7"/>
</Storyboard> </Storyboard>
</VisualState> </VisualState>
</VisualStateGroup> </VisualStateGroup>
<!-- ControlPanel Visibility states -->
<!-- Media state visual states -->
<VisualStateGroup x:Name="MediaStates"> <VisualStateGroup x:Name="MediaStates">
<VisualState x:Name="Normal" /> <VisualState x:Name="Normal" />
<VisualState x:Name="Buffering"> <VisualState x:Name="Buffering">
<Storyboard> <Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="BufferingProgressBar"> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="BufferingProgressBar">
<DiscreteObjectKeyFrame KeyTime="0"> <DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames> </ObjectAnimationUsingKeyFrames>
</Storyboard> </Storyboard>
</VisualState> </VisualState>
<VisualState x:Name="Loading"> <VisualState x:Name="Loading">
<Storyboard> <Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="BufferingProgressBar"> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="BufferingProgressBar">
<DiscreteObjectKeyFrame KeyTime="0"> <DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames> </ObjectAnimationUsingKeyFrames>
<DoubleAnimation Storyboard.TargetName="ProgressSlider" <DoubleAnimation Storyboard.TargetName="ProgressSlider"
Storyboard.TargetProperty="Opacity" Storyboard.TargetProperty="Opacity"
@@ -439,24 +198,6 @@
</VisualState> </VisualState>
</VisualStateGroup> </VisualStateGroup>
<!-- Focus states -->
<VisualStateGroup x:Name="FocusStates">
<VisualState x:Name="Focused">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="FocusVisualWhite"
Storyboard.TargetProperty="Opacity"
To="1"
Duration="0" />
<DoubleAnimation Storyboard.TargetName="FocusVisualBlack"
Storyboard.TargetProperty="Opacity"
To="1"
Duration="0" />
</Storyboard>
</VisualState>
<VisualState x:Name="Unfocused" />
<VisualState x:Name="PointerFocused" />
</VisualStateGroup>
<VisualStateGroup x:Name="PlayPauseStates"> <VisualStateGroup x:Name="PlayPauseStates">
<VisualState x:Name="PlayState" /> <VisualState x:Name="PlayState" />
<VisualState x:Name="PauseState"> <VisualState x:Name="PauseState">
@@ -467,7 +208,6 @@
</Storyboard> </Storyboard>
</VisualState> </VisualState>
</VisualStateGroup> </VisualStateGroup>
<!-- FullWindow states -->
<VisualStateGroup x:Name="FullWindowStates"> <VisualStateGroup x:Name="FullWindowStates">
<VisualState x:Name="NonFullWindowState" /> <VisualState x:Name="NonFullWindowState" />
<VisualState x:Name="FullWindowState"> <VisualState x:Name="FullWindowState">
@@ -481,161 +221,132 @@
</VisualStateManager.VisualStateGroups> </VisualStateManager.VisualStateGroups>
<Border x:Name="Border"> <Border x:Name="Border">
<Grid x:Name="ControlPanelGrid"> <Border.Resources>
<Grid.Resources>
<Style TargetType="Button" BasedOn="{StaticResource ButtonRevealStyle}"> <Style TargetType="Button" BasedOn="{StaticResource ButtonRevealStyle}">
<Setter Property="Width" Value="50"/> <Setter Property="Width" Value="50"/>
<Setter Property="Height" Value="50"/> <Setter Property="Height" Value="50"/>
<Setter Property="FontSize" Value="20"/>
<Setter Property="Background" Value="Transparent"/> <Setter Property="Background" Value="Transparent"/>
<Setter Property="FontFamily" Value="Segoe UI, Segoe MDL2 Assets"/>
</Style> </Style>
</Grid.Resources> </Border.Resources>
<Grid x:Name="ControlPanelGrid">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="auto"/> <RowDefinition Height="Auto"/>
<RowDefinition/> <RowDefinition/>
<RowDefinition Height="auto"/> <RowDefinition Height="Auto"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid x:Name="header"> <Grid x:Name="header">
<Grid.RenderTransform>
<TranslateTransform x:Name="TranslateVerticalTop"/>
</Grid.RenderTransform>
<Grid.Background> <Grid.Background>
<AcrylicBrush TintColor="#CC000000" TintOpacity=".6"/> <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="Black"/>
<GradientStop Color="#00000000" Offset="1"/>
</LinearGradientBrush>
</Grid.Background> </Grid.Background>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/> <ColumnDefinition Width="Auto"/>
<ColumnDefinition/> <ColumnDefinition/>
<ColumnDefinition Width="auto"/> <ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Button x:Name="minimize"> <Button x:Name="MinimizeButton" HorizontalAlignment="Left" VerticalAlignment="Top" Content="&#xE011;"/>
<FontIcon Glyph="&#xE011;"/>
</Button>
<StackPanel Margin="10,0" Grid.Column="1" VerticalAlignment="Center"> <StackPanel VerticalAlignment="Top" Grid.Column="1" Margin="10,0">
<TextBlock x:Name="title" TextTrimming="CharacterEllipsis" FontSize="20" MaxLines="1"/> <TextBlock x:Name="title" Text="Name" TextTrimming="CharacterEllipsis" MaxLines="1" Style="{StaticResource TitleTextBlockStyle}"/>
<TextBlock Foreground="LightGray" FontStyle="Italic" x:Name="author"/> <TextBlock x:Name="channel" Text="Channel" TextTrimming="CharacterEllipsis" FontStyle="Italic"/>
</StackPanel> </StackPanel>
<StackPanel Orientation="Horizontal" Grid.Column="2" x:Name="headerToolbar"> <StackPanel x:Name="RightHeaderControls" Orientation="Horizontal" VerticalAlignment="Top" Grid.Column="2">
<Button x:Name="close"> <Button x:Name="CloseButton" HorizontalAlignment="Right" VerticalAlignment="Top" Content="&#xE106;"/>
<FontIcon Glyph="&#xE106;"/> <Button x:Name="CastButton" Content="&#xEC15;"/>
</Button> <Button x:Name="CompactOverlayButton" HorizontalAlignment="Right" VerticalAlignment="Top" Content="&#xE2B3;"/>
<Button x:Name="CastButton">
<FontIcon Glyph="&#xEC15;"/>
</Button>
<Button x:Name="CompactOverlayButton" VerticalAlignment="Top" HorizontalAlignment="Right">
<FontIcon Glyph="&#xE2B3;"/>
</Button>
</StackPanel> </StackPanel>
</Grid> </Grid>
<Grid Grid.Row="1" x:Name="centerBgr" Visibility="Collapsed" IsHitTestVisible="False" Background="#66000000"/> <Grid x:Name="center" Grid.Row="1" Visibility="Collapsed" Background="#7F000000">
<Grid Grid.Row="1" x:Name="center" Visibility="Collapsed"> <Button x:Name="drag" IsHitTestVisible="False" Height="32" Width="47" Margin="0,0,47,0" Content="&#xE700;" Visibility="Collapsed" Padding="0" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" VerticalAlignment="Top" HorizontalAlignment="Right"/>
<Button x:Name="maximize" VerticalAlignment="Top" HorizontalAlignment="Left" IsHitTestVisible="True"> <StackPanel x:Name="centerControls" Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<FontIcon Glyph="&#xE010;"/> <ProgressBar x:Name="SeekIndicator" Background="Transparent" VerticalAlignment="Bottom"/>
</Button>
<Button x:Name="compactClose" VerticalAlignment="Top" HorizontalAlignment="Right">
<FontIcon Glyph="&#xE106;"/>
</Button>
<Button Height="32" Width="50" Margin="0,0,48,0" VerticalAlignment="Top" HorizontalAlignment="Right" Visibility="Collapsed" FontFamily="Segoe MDL2 Assets" Content="&#xE700;" IsHitTestVisible="False" x:Name="dragholder"/>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center" x:Name="centralStack"/>
<ProgressBar VerticalAlignment="Bottom" x:Name="compactSeek" Background="Transparent"/>
</Grid> </Grid>
<Grid Grid.Row="2" x:Name="footer"> <Grid x:Name="footer" Grid.Row="2">
<Grid.RenderTransform>
<TranslateTransform x:Name="TranslateVerticalBottom"/>
</Grid.RenderTransform>
<Grid.Background> <Grid.Background>
<AcrylicBrush TintColor="#CC000000" TintOpacity=".6"/> <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="Black" Offset="1"/>
<GradientStop Color="#00000000" Offset="0"/>
</LinearGradientBrush>
</Grid.Background> </Grid.Background>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/> <ColumnDefinition Width="Auto"/>
<ColumnDefinition/> <ColumnDefinition/>
<ColumnDefinition Width="auto"/> <ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" x:Name="leftStack"> <ProgressBar x:Name="BufferingProgressBar" VerticalAlignment="Bottom" Grid.ColumnSpan="3" Background="Transparent" IsIndeterminate="True" Visibility="Collapsed"/>
<StackPanel x:Name="LeftFooterControls" Orientation="Horizontal" VerticalAlignment="Bottom">
<Button x:Name="PlayPauseButton"> <Button x:Name="PlayPauseButton">
<SymbolIcon x:Name="PlayPauseSymbol" Symbol="Play"/> <SymbolIcon x:Name="PlayPauseSymbol" Symbol="Play"/>
</Button> </Button>
<Button x:Name="next"> <Button x:Name="NextButton" Content="&#xE101;"/>
<SymbolIcon Symbol="Next"/> <Button x:Name="VolumeMenuButton" Content="&#xE15D;">
</Button>
<Button x:Name="volume">
<FontIcon Glyph="&#xE15D;"/>
<Button.Flyout> <Button.Flyout>
<Flyout> <Flyout>
<StackPanel Orientation="Horizontal" Margin="-10"> <StackPanel Orientation="Horizontal" Margin="-10">
<Button x:Name="AudioMuteButton" Width="50" Height="50" Background="Transparent" FontFamily="Segoe MDL2 Assets" FontSize="25"> <Button x:Name="AudioMuteButton" Content="&#xE15D;" FontFamily="Segoe MDL2 Assets" Height="50" Width="50" Background="Transparent" FontSize="20"/>
<FontIcon Glyph="&#xE15D;"/> <Slider x:Name="VolumeSlider" Width="150" Margin="10,5,10,0" VerticalAlignment="Center" TickPlacement="Outside" TickFrequency="10"/>
</Button>
<Slider Foreground="Red" Orientation="Horizontal" Width="150" Margin="10,5,10,0" VerticalAlignment="Center" x:Name="VolumeSlider"/>
</StackPanel> </StackPanel>
</Flyout> </Flyout>
</Button.Flyout> </Button.Flyout>
</Button> </Button>
<Button x:Name="goLive" Width="NaN" Visibility="Collapsed"> <Button x:Name="PlayLiveButton" Width="NaN" Visibility="Collapsed">
<TextBlock x:Uid="/VideoPage/live" Text="🔴 LIVE"/> <TextBlock x:Uid="/VideoPage/live" Text="🔴 LIVE"/>
</Button> </Button>
</StackPanel> </StackPanel>
<Grid Grid.Column="1" Margin="10,5" x:Name="sliderPan"> <Grid x:Name="CenterFooterControls" Height="50" Grid.Column="1" VerticalAlignment="Bottom">
<TextBlock VerticalAlignment="Bottom" HorizontalAlignment="Left" x:Name="TimeElapsedElement" Text="00:00"/> <Grid.RowDefinitions>
<TextBlock VerticalAlignment="Bottom" HorizontalAlignment="Right" x:Name="TimeRemainingElement" Text="00:00"/> <RowDefinition/>
<Grid VerticalAlignment="Top" Height="4" Margin="0,15,0,0"> <RowDefinition/>
<ProgressBar Background="#66FFFFFF" Foreground="#66FFFFFF" x:Name="BufferingProgressBar"/> </Grid.RowDefinitions>
</Grid> <TextBlock Grid.Row="1" Margin="5,0" VerticalAlignment="Center" HorizontalAlignment="Left" x:Name="TimeElapsedElement" Text="00:00"/>
<Slider x:Name="ProgressSlider" Style="{StaticResource PlayerSeek}" IsThumbToolTipEnabled="False" Background="Transparent" HorizontalAlignment="Stretch" VerticalAlignment="Top"/> <TextBlock Grid.Row="1" Margin="5,0" VerticalAlignment="Center" HorizontalAlignment="Right" x:Name="TimeRemainingElement" Text="00:00"/>
<Slider x:Name="ProgressSlider" Style="{StaticResource PlayerSeek}" IsThumbToolTipEnabled="False" HorizontalAlignment="Stretch" VerticalAlignment="Center" Margin="5,0"/>
</Grid> </Grid>
<StackPanel Orientation="Horizontal" Grid.Column="2" x:Name="rightStack"> <StackPanel x:Name="RightFooterControls" Grid.Column="2" VerticalAlignment="Bottom" Orientation="Horizontal">
<Button x:Name="SkipBackwardButton"> <Button x:Name="SkipBackwardButton" Content="&#xED3C;"/>
<FontIcon Glyph="&#xED3C;"/> <Button x:Name="SkipForwardButton" Content="&#xED3D;"/>
</Button>
<Button x:Name="SkipForwardButton">
<FontIcon Glyph="&#xED3D;"/>
</Button>
<Line Stroke="White" StrokeThickness="2" Y1="5" Y2="45"/> <Line Stroke="White" StrokeThickness="2" Y1="5" Y2="45"/>
<Button x:Name="CaptionsMenuButton" Content="&#xE7F0;">
<Button x:Name="cc">
<SymbolIcon Symbol="ClosedCaption"/>
<Button.Flyout> <Button.Flyout>
<Flyout> <Flyout>
<StackPanel Width="225"> <StackPanel Width="225">
<ToggleSwitch x:Name="ccSwitch" OnContent="Subtitles" OffContent="Subtitles" x:Uid="/VideoPage/subsSwitch"/> <ToggleSwitch x:Name="CaptionsToggleSwitch" OnContent="Subtitles" OffContent="Subtitles" x:Uid="/VideoPage/subsSwitch"/>
<ComboBox x:Name="ccSelector" Header="Language" x:Uid="/VideoPage/subsSelector" PlaceholderText="No subtitles are available" HorizontalAlignment="Stretch"/> <ComboBox x:Name="CaptionsSelector" Header="Language" x:Uid="/VideoPage/subsSelector" PlaceholderText="No captions are available" HorizontalAlignment="Stretch"/>
</StackPanel> </StackPanel>
</Flyout> </Flyout>
</Button.Flyout> </Button.Flyout>
</Button> </Button>
<Button x:Name="QualityMenuButton" Content="&#xE115;">
<Button x:Name="quality">
<SymbolIcon Symbol="Setting"/>
<Button.Flyout> <Button.Flyout>
<Flyout> <Flyout>
<ComboBox Width="225" x:Uid="/VideoPage/qualitySelector" Header="Quality" x:Name="qualitySelector"/> <ComboBox Width="225" x:Name="QualitySelector" Header="Language" x:Uid="/VideoPage/qualitySelector"/>
</Flyout> </Flyout>
</Button.Flyout> </Button.Flyout>
</Button> </Button>
<Button x:Name="FullWindowButton"> <Button x:Name="FullWindowButton">
<SymbolIcon x:Name="FullWindowSymbol" Symbol="FullScreen"/> <SymbolIcon x:Name="FullWindowSymbol" Symbol="FullScreen"/>
</Button> </Button>
</StackPanel> </StackPanel>
</Grid> </Grid>
</Grid> </Grid>
</Border> </Border>
<controls:LiveCaptions Visibility="Collapsed" x:Name="captions"/>
<adverts:PlayerAdvert Grid.Row="1" x:Name="ad" VerticalAlignment="Bottom"/> <controls:LiveCaptions Visibility="Collapsed" x:Name="CaptionControl"/>
<adverts:PlayerAdvert Grid.Row="1" x:Name="AdvertControl" VerticalAlignment="Bottom"/>
</Grid> </Grid>
</ControlTemplate> </ControlTemplate>
</Setter.Value> </Setter.Value>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 MiB

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 MiB

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 MiB

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 836 KiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 MiB

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 MiB

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 MiB

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 818 KiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 419 KiB

After

Width:  |  Height:  |  Size: 398 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 MiB

After

Width:  |  Height:  |  Size: 3.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 MiB

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 MiB

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 MiB

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 MiB

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 MiB

After

Width:  |  Height:  |  Size: 3.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 MiB

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 MiB

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 MiB

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 MiB

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 529 KiB

After

Width:  |  Height:  |  Size: 370 KiB