Skip to content

Commit b7fecab

Browse files
authored
支持使用内建网页视图进行视频播放 (#267)
* 提供网页播放能力 * 完善网页播放器的支持
1 parent 8ed14b1 commit b7fecab

21 files changed

+454
-19
lines changed

src/App/App.csproj

+9
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
<None Remove="Assets\qq_group_dark.jpg" />
4747
<None Remove="Assets\qq_group_light.jpg" />
4848
<None Remove="Assets\ReaderClean.js" />
49+
<None Remove="Assets\WebPlayer.js" />
4950
<None Remove="Controls\AppTitleBar.xaml" />
5051
<None Remove="Controls\Base\BiliPlayer\BiliPlayer.xaml" />
5152
<None Remove="Controls\Base\ChatMessageItemControl.xaml" />
@@ -142,6 +143,7 @@
142143
<None Remove="Controls\Settings\ThemeSettingSection.xaml" />
143144
<None Remove="Controls\Settings\TraditionalChineseSettingSection.xaml" />
144145
<None Remove="Controls\Settings\WebDavSettingSection.xaml" />
146+
<None Remove="Controls\Settings\WebPlayerSettingSection.xaml" />
145147
<None Remove="Controls\TipDialog.xaml" />
146148
<None Remove="Controls\TipPopup.xaml" />
147149
<None Remove="Controls\UpdateDialog.xaml" />
@@ -175,6 +177,7 @@
175177
<None Remove="Pages\ViewLaterPage.xaml" />
176178
<None Remove="Pages\WebDavPage.xaml" />
177179
<None Remove="Pages\WebDavPlayerPage.xaml" />
180+
<None Remove="Pages\WebPlayerPage.xaml" />
178181
<None Remove="Pages\WebSignInPage.xaml" />
179182
<None Remove="Styles\Overwrites.xaml" />
180183
<None Remove="Styles\TrayResources.xaml" />
@@ -287,6 +290,12 @@
287290
<Content Update="Assets\AppCenterSecret.txt">
288291
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
289292
</Content>
293+
<Page Update="Pages\WebPlayerPage.xaml">
294+
<Generator>MSBuild:Compile</Generator>
295+
</Page>
296+
<Page Update="Controls\Settings\WebPlayerSettingSection.xaml">
297+
<Generator>MSBuild:Compile</Generator>
298+
</Page>
290299
<Page Update="Pages\WebSignInPage.xaml">
291300
<Generator>MSBuild:Compile</Generator>
292301
</Page>

src/App/Assets/WebPlayer.js

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
let elements = ['.video-page-game-card-small', '.bili-header', '.ad-report', '.video-card-ad-small', '.vcd', '.link-navbar-ctnr'];
2+
let isFullWindowClicked = false;
3+
4+
function hideElements() {
5+
elements.forEach(function (selector) {
6+
let element = document.querySelector(selector);
7+
if (element) {
8+
element.style.display = 'none';
9+
}
10+
})
11+
12+
let header = document.getElementById("biliMainHeader");
13+
header.style.height = "12px";
14+
}
15+
16+
function fullWindow() {
17+
var div = document.querySelector('.bpx-player-ctrl-btn.bpx-player-ctrl-web');
18+
if (div) {
19+
div.click();
20+
isFullWindowClicked = true;
21+
}
22+
}
23+
24+
// 创建 MutationObserver 对象
25+
const observer = new MutationObserver(function (mutationsList, observer) {
26+
let isMatched = false;
27+
// 遍历每个发生变化的 mutation
28+
for (let mutation of mutationsList) {
29+
// 检查是否有新增的节点
30+
if (mutation.type === 'childList') {
31+
32+
if (!isFullWindowClicked) {
33+
fullWindow();
34+
}
35+
36+
if (mutation.addedNodes.length > 0) {
37+
mutation.addedNodes.forEach(function (node) {
38+
// 检查新增的节点是否匹配 hideElements 中的选择器
39+
elements.forEach(function (selector) {
40+
if (node.matches && node.matches(selector)) {
41+
// 调用 hideElements 方法
42+
isMatched = true;
43+
hideElements();
44+
}
45+
});
46+
});
47+
}
48+
}
49+
}
50+
});
51+
52+
let lastHideElementsTime = 0;
53+
54+
// 监听滚动事件
55+
window.addEventListener('scroll', function () {
56+
// 获取当前时间戳
57+
const currentTime = Date.now();
58+
59+
// 检查距离上次触发 hideElements 是否超过300ms
60+
if (currentTime - lastHideElementsTime >= 300) {
61+
// 更新上次触发 hideElements 的时间戳
62+
lastHideElementsTime = currentTime;
63+
64+
// 调用 hideElements 方法
65+
hideElements();
66+
}
67+
});
68+
69+
// 开始观察整个文档的DOM变化
70+
observer.observe(document.documentElement, { childList: true, subtree: true });
71+
hideElements();
72+
fullWindow();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<local:SettingSection
3+
x:Class="Bili.Copilot.App.Controls.Settings.WebPlayerSettingSection"
4+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
5+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
6+
xmlns:base="using:Bili.Copilot.App.Controls.Base"
7+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
8+
xmlns:ext="using:Bili.Copilot.App.Extensions"
9+
xmlns:labs="using:CommunityToolkit.Labs.WinUI"
10+
xmlns:local="using:Bili.Copilot.App.Controls.Settings"
11+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
12+
xmlns:player="using:Bili.Copilot.Models.Constants.Player"
13+
mc:Ignorable="d">
14+
15+
<labs:SettingsExpander Description="{ext:Locale Name=UseWebPlayerDescription}" Header="{ext:Locale Name=UseWebPlayer}">
16+
<labs:SettingsExpander.HeaderIcon>
17+
<base:FluentIcon Symbol="GlobeVideo" />
18+
</labs:SettingsExpander.HeaderIcon>
19+
<ToggleSwitch IsEnabled="{x:Bind ViewModel.IsWebSignIn, Mode=OneWay}" IsOn="{x:Bind ViewModel.UseWebPlayer, Mode=TwoWay}" />
20+
<labs:SettingsExpander.Items>
21+
<labs:SettingsCard Description="{x:Bind ViewModel.WebSignInStatus, Mode=OneWay}" Header="{ext:Locale Name=CheckWebSignIn}">
22+
<Button
23+
MinWidth="120"
24+
Click="OnVerifyButtonClick"
25+
Content="{ext:Locale Name=Check}" />
26+
</labs:SettingsCard>
27+
</labs:SettingsExpander.Items>
28+
</labs:SettingsExpander>
29+
</local:SettingSection>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright (c) Bili Copilot. All rights reserved.
2+
3+
using Bili.Copilot.ViewModels;
4+
5+
namespace Bili.Copilot.App.Controls.Settings;
6+
7+
/// <summary>
8+
/// 网页播放器设置部分.
9+
/// </summary>
10+
public sealed partial class WebPlayerSettingSection : SettingSection
11+
{
12+
/// <summary>
13+
/// Initializes a new instance of the <see cref="WebPlayerSettingSection"/> class.
14+
/// </summary>
15+
public WebPlayerSettingSection() => InitializeComponent();
16+
17+
private void OnVerifyButtonClick(object sender, RoutedEventArgs e)
18+
=> AppViewModel.Instance.VerifyWebSignInCommand.Execute(default);
19+
}

src/App/Forms/MainWindow.xaml.cs

+25-1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ public MainWindow()
5757
_appViewModel.RequestRead += OnRequestRead;
5858
_appViewModel.RequestShowImages += OnRequestShowImages;
5959
_appViewModel.BackRequest += OnBackRequestedAsync;
60+
_appViewModel.RequestVerifyWebSignIn += OnRequestVerifyWebSignIn;
6061

6162
MinWidth = 800;
6263
MinHeight = 640;
@@ -127,6 +128,9 @@ private static PlayerWindow GetPlayerWindow()
127128
private static bool IsMainWindowPlayer()
128129
=> SettingsToolkit.ReadLocalSetting(SettingNames.PlayerWindowBehaviorType, PlayerWindowBehavior.Main) == PlayerWindowBehavior.Main;
129130

131+
private static bool IsWebPlayer()
132+
=> SettingsToolkit.ReadLocalSetting(SettingNames.UseWebPlayer, false);
133+
130134
private void OnTitleBarLoaded(object sender, RoutedEventArgs e)
131135
{
132136
if (_isInitialized)
@@ -192,7 +196,7 @@ private async void OnAppViewModelRequestShowUpdateDialogAsync(object sender, Upd
192196

193197
private void OnAppViewModelRequestPlay(object sender, PlaySnapshot e)
194198
{
195-
if (IsMainWindowPlayer())
199+
if (IsMainWindowPlayer() && !IsWebPlayer())
196200
{
197201
BeforeEnterPlayerPageAsync();
198202
PlayerUtils.InitializePlayer(e, PlayerFrame, this);
@@ -274,6 +278,17 @@ private void OnRequestShowFans(object sender, UserProfile e)
274278
}
275279
}
276280

281+
private void OnRequestVerifyWebSignIn(object sender, EventArgs e)
282+
{
283+
Activate();
284+
MainSplitView.IsPaneOpen = false;
285+
286+
if (OverlayFrame.Content is not WebSignInPage)
287+
{
288+
_ = OverlayFrame.Navigate(typeof(WebSignInPage));
289+
}
290+
}
291+
277292
private void OnRequestShowFavorites(object sender, FavoriteType e)
278293
{
279294
Activate();
@@ -362,6 +377,15 @@ private async void OnBackRequestedAsync(object sender, EventArgs e)
362377
await Task.Delay(200);
363378
CustomTitleBar.Refresh();
364379
}
380+
else if (MainFrame.Content is SettingsPage settingsPage)
381+
{
382+
if (OverlayFrame.Content is WebSignInPage)
383+
{
384+
OverlayFrame.Navigate(typeof(Page));
385+
}
386+
387+
settingsPage.ViewModel.WebPlayerInit();
388+
}
365389
}
366390

367391
private void OnActiveMainWindow(object sender, EventArgs e)

src/App/Pages/SettingsPage.xaml

+5-4
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,10 @@
3838
HorizontalAlignment="Left"
3939
Style="{StaticResource BodyStrongTextBlockStyle}"
4040
Text="{ext:Locale Name=Player}" />
41-
<settings:PlayerModeSettingSection ViewModel="{x:Bind ViewModel}" />
42-
<settings:PlayerControlSettingSection ViewModel="{x:Bind ViewModel}" />
43-
<settings:RoamingSettingSection ViewModel="{x:Bind ViewModel}" />
41+
<settings:WebPlayerSettingSection ViewModel="{x:Bind ViewModel}" />
42+
<settings:PlayerModeSettingSection IsEnabled="{x:Bind ViewModel.UseWebPlayer, Mode=OneWay, Converter={StaticResource ObjectToBoolReverseConverter}}" ViewModel="{x:Bind ViewModel}" />
43+
<settings:PlayerControlSettingSection IsEnabled="{x:Bind ViewModel.UseWebPlayer, Mode=OneWay, Converter={StaticResource ObjectToBoolReverseConverter}}" ViewModel="{x:Bind ViewModel}" />
44+
<settings:RoamingSettingSection IsEnabled="{x:Bind ViewModel.UseWebPlayer, Mode=OneWay, Converter={StaticResource ObjectToBoolReverseConverter}}" ViewModel="{x:Bind ViewModel}" />
4445

4546
<TextBlock
4647
Margin="0,8,0,4"
@@ -59,7 +60,7 @@
5960
<settings:NotificationSettingSection ViewModel="{x:Bind ViewModel}" />
6061
<settings:LoggerSettingSection ViewModel="{x:Bind ViewModel}" />
6162
<settings:CacheSettingSection ViewModel="{x:Bind ViewModel}" />
62-
<settings:TraditionalChineseSettingSection ViewModel="{x:Bind ViewModel}" />
63+
<settings:TraditionalChineseSettingSection IsEnabled="{x:Bind ViewModel.UseWebPlayer, Mode=OneWay, Converter={StaticResource ObjectToBoolReverseConverter}}" ViewModel="{x:Bind ViewModel}" />
6364

6465
<TextBlock
6566
Margin="0,16,0,8"

src/App/Pages/WebPlayerPage.xaml

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<base:PageBase
3+
x:Class="Bili.Copilot.App.Pages.WebPlayerPage"
4+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
5+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
6+
xmlns:base="using:Bili.Copilot.App.Controls.Base"
7+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
8+
xmlns:ext="using:Bili.Copilot.App.Extensions"
9+
xmlns:local="using:Bili.Copilot.App.Pages"
10+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
11+
RequestedTheme="Light"
12+
mc:Ignorable="d">
13+
14+
<Grid>
15+
<WebView2 x:Name="MainView" NavigationCompleted="OnNavigationCompletedAsync" />
16+
<base:LoadingOverlapper
17+
x:Name="LoadingOverlay"
18+
IsOpen="False"
19+
Text="{ext:Locale Name=LoadingAndWait}" />
20+
</Grid>
21+
</base:PageBase>

src/App/Pages/WebPlayerPage.xaml.cs

+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// Copyright (c) Bili Copilot. All rights reserved.
2+
3+
using Bili.Copilot.App.Controls.Base;
4+
using Bili.Copilot.Models.App.Args;
5+
using Microsoft.Web.WebView2.Core;
6+
using Windows.Storage;
7+
8+
namespace Bili.Copilot.App.Pages;
9+
10+
/// <summary>
11+
/// 网页播放器界面.
12+
/// </summary>
13+
public sealed partial class WebPlayerPage : PageBase
14+
{
15+
private const string UserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 Edg/118.0.2048.1";
16+
private string _url;
17+
private Window _attachedWindow;
18+
19+
/// <summary>
20+
/// Initializes a new instance of the <see cref="WebPlayerPage"/> class.
21+
/// </summary>
22+
public WebPlayerPage()
23+
{
24+
InitializeComponent();
25+
}
26+
27+
/// <inheritdoc/>
28+
protected override void OnNavigatedTo(NavigationEventArgs e)
29+
{
30+
if (e.Parameter is PlayerPageNavigateEventArgs args)
31+
{
32+
if (args.Snapshot != null)
33+
{
34+
_url = args.Snapshot.WebLink;
35+
_attachedWindow = args.AttachedWindow as Window;
36+
}
37+
}
38+
}
39+
40+
/// <inheritdoc/>
41+
protected override void OnNavigatedFrom(NavigationEventArgs e)
42+
{
43+
try
44+
{
45+
MainView.NavigateToString(string.Empty);
46+
}
47+
catch (Exception)
48+
{
49+
}
50+
}
51+
52+
/// <inheritdoc/>
53+
protected override async void OnPageLoaded()
54+
{
55+
LoadingOverlay.IsOpen = true;
56+
57+
// 取消自动静音限制.
58+
Environment.SetEnvironmentVariable("WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS", "--autoplay-policy=no-user-gesture-required --enable-features=PlatformHEVCEncoderSupport --enable-features=HardwareMediaDecoding --enable-features=PlatformEncryptedDolbyVision");
59+
await MainView.EnsureCoreWebView2Async();
60+
MainView.CoreWebView2.Settings.IsStatusBarEnabled = false;
61+
MainView.CoreWebView2.Settings.AreDefaultContextMenusEnabled = true;
62+
MainView.CoreWebView2.Settings.AreDevToolsEnabled = true;
63+
MainView.CoreWebView2.Settings.UserAgent = UserAgent;
64+
MainView.CoreWebView2.ContainsFullScreenElementChanged += OnContainsFullScreenElementChanged;
65+
MainView.CoreWebView2.AddWebResourceRequestedFilter("*", CoreWebView2WebResourceContext.All);
66+
MainView.CoreWebView2.WebResourceRequested += OnWebResourceRequested;
67+
MainView.CoreWebView2.NavigationStarting += OnNavigationStarting;
68+
if (!string.IsNullOrEmpty(_url))
69+
{
70+
MainView.CoreWebView2.Navigate(_url);
71+
}
72+
}
73+
74+
private void OnNavigationStarting(CoreWebView2 sender, CoreWebView2NavigationStartingEventArgs args)
75+
=> args.RequestHeaders.SetHeader("User-Agent", UserAgent);
76+
77+
private void OnWebResourceRequested(CoreWebView2 sender, CoreWebView2WebResourceRequestedEventArgs args)
78+
=> args.Request.Headers.SetHeader("User-Agent", UserAgent);
79+
80+
private void OnContainsFullScreenElementChanged(CoreWebView2 sender, object args)
81+
{
82+
if (sender.ContainsFullScreenElement)
83+
{
84+
_attachedWindow.AppWindow.SetPresenter(Microsoft.UI.Windowing.AppWindowPresenterKind.FullScreen);
85+
}
86+
else
87+
{
88+
_attachedWindow.AppWindow.SetPresenter(Microsoft.UI.Windowing.AppWindowPresenterKind.Overlapped);
89+
}
90+
}
91+
92+
private async void OnNavigationCompletedAsync(WebView2 sender, CoreWebView2NavigationCompletedEventArgs args)
93+
{
94+
LoadingOverlay.IsOpen = false;
95+
96+
// 注入 js.
97+
var cleanFile = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Assets/WebPlayer.js"));
98+
var cleanContent = await FileIO.ReadTextAsync(cleanFile);
99+
await MainView.CoreWebView2.ExecuteScriptAsync(cleanContent);
100+
}
101+
}

src/App/Pages/WebSignInPage.xaml

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
IsOpen="False"
1818
Text="{ext:Locale Name=LoadingAndWait}" />
1919
<Button
20+
x:Name="BackButton"
2021
Width="40"
2122
Height="40"
2223
Margin="24"

0 commit comments

Comments
 (0)