The Plan of Our Final Project,NEU-TV Player

小组头脑风暴了几个idea,最终经过投票,在幽灵般的第五票下决定了我们的项目主题:NEU-TV Player。项目将在我们已有的视频播放器经验上进行开发,并添加一些有趣的功能。

项目描述

描述

一个基于东北大学IPV6视频直播源的弹幕视频播放器。

功能

观看节目

涵盖中央、地方各大卫视频道,内容覆盖电影、音乐、体育、卡通应有尽有。只要连接校园网,打开本应用,在电视墙内选择你中意的频道,轻轻一点尽享缤纷精彩。

频道搜索

频道太多眼花缭乱找不到想要的那一个?有想看的节目却不知道是在哪个频道?想回看某部电影却忘了之前是在什么时候播出?只要点击搜索栏,输入你想看的频道或节目名称,我们为你锁定它!

弹幕吐槽

快乐分享给他人会加倍快乐,那么和别人一起吐槽就更是爽翻天啦。不管是背景科普还是高能预警,通过弹幕分享你的观看体验,即使是一个人也能享受大家一起看电视的快乐!

收藏回看

最近在追某项比赛,然而每次都要搜索体育频道很麻烦呐!不要急,收藏这个频道,让你直接追上比赛热点。
最近在追某部剧,然鹅某一天学业繁忙漏看了!不要方,点开节目单回看即可,最长支持7天内的视频回看哟,可以养肥后周末一口气追更。

特色

4-video

免流量

只要连接校园网,不登录账号也可直接观看视频。追剧看球再也不怕流量不够了!校园网完美覆盖,走到哪里看到哪里。

无广告

超清流畅无码节目源点开即看,没有烦人的弹窗广告,也没有煎熬的等待90s,毕竟你的时间,非常宝贵。

可互动

通过弹幕和小伙伴分享自己的观影体验,吐槽视频的各种梗,你的弹幕可能比视频更加好玩!

可补漏

收藏你喜欢的频道,免去搜索的麻烦。回看一周内的视频,错过直播也不怕!

项目设计

主界面

4-design1
上方是标签页,通过标签页来对不同类别频道进行分类展示。标签页右侧是一个小的搜索栏。标签页下是页面主体,通过照片墙的方式来展示该类别的各个频道。点击照片即可进入对应频道观看视频。

搜索页

4-design2
通过点击主页面的搜索栏可以转到搜索功能,动画是搜索栏向左延长横跨整个页面,上方的标签页消失,下方照片墙展示搜索结果。

播放页

4-design3
播放页面主要分为两部分,左侧为节目单,右侧为播放器。左侧,通过标签页展示周一到周日七天的节目单,对应日期的节目单通过垂直方向的列表显示,点击某一节目即可在播放器中回看该节目。
右侧,播放器类似于普通播放器,有播放/暂停按钮,进度条,音量调整,全屏按钮等。播放器下方是弹幕功能区。主要有一个文本编辑框和发送按钮构成 。文本框内输入文字,并选择不同的弹幕格式,然后点击发送按钮即可发送弹幕。

市场调查

小方对三位小伙伴进行了采访。先通过视频介绍我们的App,然后提问一些相关问题来帮助我们进行调整,最后将视频剪辑打码发布。
一个制作精良超级棒的视频

但是分析采访内容,大家对我们的鼓励支持远远大于批评和建议,我们自己又深刻反思分析了一下,提出如下改进:

搜索页面改进

搜索功能应当细化到具体节目才更有意义,但是照片墙只能展示到频道。所以对之前通过照片墙展示搜索结果的方式进行改进。点击搜索按钮后跳至新的页面,通过列表展示搜索结果。

播放控制改进

在播放器上,因为新加了弹幕功能,所以相应的也要加上关闭弹幕的功能。需要重写控制器。

技术分析

在确定设计之后,代锟dalao根据设计图与讨论的功能,对每个页面可能用到的技术及实现方法进行了分析。

主页面

4-design1

标签页

不得不说,我最喜欢的浏览器是firefox和chrome,尤其是chrome的标签页的设计实在是太讨喜了,一点也不拖泥带水。然而,我google了半天,没有找到等腰梯形可重叠标签页的uwp现有的成熟实现方案。捉急之下我询问了老师,
然后找到了类似的解决方案
大体的解决方案是这样的:

1
2
3
4
5
6
7
<ListBox>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>

用一个stackpanel做模板?大概是这个意思,把它水平放过来

1
2
3
4
5
6
7
8
9
10
<Style TargetType="ListBox">
<!-- Rest of the style -->
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>

增加style
用一个水平方向排列的ListBox来解决标签页的问题,然而,仅仅拥有标签页是不够的

然而,这并不能解决这个问题
lablePage

没错,根本无法实现这种边的交替效果,而且也不具有chrome的那种随着标签页增加而标签缩短的效果。
好菜啊,从没有学过uwp的我,居然要定制UI耶QAQ。
那算了,实际一点,先使用edge和firefox那种扁平化的风格堆叠吧,那个稍微简单,而且我们先不考虑标签页增加而标签缩短的问题。

搜索栏

这个功能可以使用AutoSuggestBox来实现
basic code:

1
public sealed class AutoSuggestBox : ItemsControl, IAutoSuggestBox, IAutoSuggestBox2, IAutoSuggestBox3

1
<AutoSuggestBox .../>

官方demo :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// C#
private void AutoSuggestBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
{
// Only get results when it was a user typing,
// otherwise assume the value got filled in by TextMemberPath
// or the handler for SuggestionChosen.
if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput)
{
//Set the ItemsSource to be your filtered dataset
//sender.ItemsSource = dataset;
}
}
private void AutoSuggestBox_SuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args)
{
// Set sender.Text. You can use args.SelectedItem to build your text string.
}
private void AutoSuggestBox_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)
{
if (args.ChosenSuggestion != null)
{
// User selected an item from the suggestion list, take an action on it here.
}
else
{
// Use args.QueryText to determine what to do.
}
}

这三个函数使用来分别监听前端搜索字体改变,搜索请求提交,给出建议等事件的处理函数

前端demo:

1
2
3
4
<AutoSuggestBox PlaceholderText="Search" QueryIcon="Find" Width="200"
TextChanged="AutoSuggestBox_TextChanged"
QuerySubmitted="AutoSuggestBox_QuerySubmitted"
SuggestionChosen="AutoSuggestBox_SuggestionChosen"/>

其中,PlaceholderText代表默认的占用字符串
Icon用的微软官方自带的图标
TextChanged=”AutoSuggestBox_TextChanged”
QuerySubmitted=”AutoSuggestBox_QuerySubmitted”
SuggestionChosen=”AutoSuggestBox_SuggestionChosen”
这三条对应后端的三个句柄(handle),用来处理相应的事件demo(but),原本在我们组dalao的设定中要点击搜索栏的时候搜索烂要有一个平滑延长的动画,然后标签页消失,最后过度到页面二的画面。我…..被水淹没不知所措。

后来了解到了一种解决方案,就是用StoryBoard做动画
具体的方法还没有做出来,但是还可以参考其他方案
https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.media.animation.storyboard
basic demo

1
public sealed class Storyboard : Timeline, IStoryboard

1
2
3
<Storyboard ...>
  oneOrMoreChildTimelines
</Storyboard>

这种控件细究起来还是有很多拓展性的
先上官方demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//using Windows.UI.Xaml.Media.Animation;
//using Windows.UI.Xaml.Shapes;
//using Windows.UI
private void Create_And_Run_Animation(object sender, RoutedEventArgs e)
{
// Create a red rectangle that will be the target
// of the animation.
Rectangle myRectangle = new Rectangle();
myRectangle.Width = 200;
myRectangle.Height = 200;
SolidColorBrush myBrush = new SolidColorBrush(Colors.Red);
myRectangle.Fill = myBrush;
// Create the transform
TranslateTransform moveTransform = new TranslateTransform();
moveTransform.X = 0;
moveTransform.Y = 0;
myRectangle.RenderTransform = moveTransform;
// Add the rectangle to the tree.
LayoutRoot.Children.Add(myRectangle);
// Create a duration of 2 seconds.
Duration duration = new Duration(TimeSpan.FromSeconds(2));
// Create two DoubleAnimations and set their properties.
DoubleAnimation myDoubleAnimationX = new DoubleAnimation();
DoubleAnimation myDoubleAnimationY = new DoubleAnimation();
myDoubleAnimationX.Duration = duration;
myDoubleAnimationY.Duration = duration;
Storyboard justintimeStoryboard = new Storyboard();
justintimeStoryboard.Duration = duration;
justintimeStoryboard.Children.Add(myDoubleAnimationX);
justintimeStoryboard.Children.Add(myDoubleAnimationY);
Storyboard.SetTarget(myDoubleAnimationX, moveTransform);
Storyboard.SetTarget(myDoubleAnimationY, moveTransform);
// Set the X and Y properties of the Transform to be the target properties
// of the two respective DoubleAnimations.
Storyboard.SetTargetProperty(myDoubleAnimationX, "X");
Storyboard.SetTargetProperty(myDoubleAnimationY, "Y");
myDoubleAnimationX.To = 200;
myDoubleAnimationY.To = 200;
// Make the Storyboard a resource.
LayoutRoot.Resources.Add("justintimeStoryboard", justintimeStoryboard);
// Begin the animation.
justintimeStoryboard.Begin();
}

这个动画一共有四个部分,创建的时候也会有动画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<StackPanel x:Name="LayoutRoot" >
<StackPanel.Resources>
<Storyboard x:Name="myStoryboard">
<DoubleAnimation From="1" To="6" Duration="00:00:6"
Storyboard.TargetName="rectScaleTransform"
Storyboard.TargetProperty="ScaleY">
<DoubleAnimation.EasingFunction>
<BounceEase Bounces="2" EasingMode="EaseOut"
Bounciness="2" />
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</StackPanel.Resources>
<!-- Button that begins animation. -->
<Button Click="Animation_Begin"
Margin="2" Content="Begin" />
<!-- Button that pauses Animation. -->
<Button Click="Animation_Pause"
Margin="2" Content="Pause" />
<!-- Button that resumes Animation. -->
<Button Click="Animation_Resume"
Margin="2" Content="Resume" />
<!-- Button that stops Animation. Stopping the animation
returns the ellipse to its original location. -->
<Button Click="Animation_Stop"
Margin="2" Content="Stop" />
<Rectangle Fill="Blue" Width="200" Height="30">
<Rectangle.RenderTransform>
<ScaleTransform x:Name="rectScaleTransform" />
</Rectangle.RenderTransform>
</Rectangle>
</StackPanel>

这个扩展性非常强,每一个事件发生都可以调用相对应的story board的产生的animation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void Animation_Begin(object sender, RoutedEventArgs e)
{
myStoryboard.Begin();
}
private void Animation_Pause(object sender, RoutedEventArgs e)
{
myStoryboard.Pause();
}
private void Animation_Resume(object sender, RoutedEventArgs e)
{
myStoryboard.Resume();
}
private void Animation_Stop(object sender, RoutedEventArgs e)
{
myStoryboard.Stop();
}

当我们设定好动画之后,就可以体验到那种顺滑的感觉

搜索页面

4-design2

搜索栏

这个我之前已经提到过了,这里不再细讲

节目缩略图

这个图片来源主要来自于后端提供,简单来说,其实就是Image这个博客就有一个很好的demo来展示如何显示出一个图片墙。
不过他是用的某种选择文件夹的方式,我们改改也就可以用了,用服务器的某个文件夹这样?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
StorageFile fileLocal = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appdata:///local/account/" + ImageHelper.folderStr + ".json"));
if (fileLocal != null)
{
try
{
//读取本地文件内容,并且反序列化
using (IRandomAccessStream readStream = await fileLocal.OpenAsync(FileAccessMode.Read))
{
using (DataReader dataReader = new DataReader(readStream))
{
UInt64 size = readStream.Size;
if (size <= UInt32.MaxValue)
{
await dataReader.LoadAsync(sizeof(Int32));
Int32 stringSize = dataReader.ReadInt32();
await dataReader.LoadAsync((UInt32)stringSize);
string fileContent = dataReader.ReadString((uint)stringSize);
ImagePath imagePath = new ImagePath(fileContent);
StorageFolder folder = await StorageApplicationPermissions.FutureAccessList.GetFolderAsync(imagePath.Path);
//筛选图片
var queryOptions = new Windows.Storage.Search.QueryOptions();
queryOptions.FileTypeFilter.Add(".png");
queryOptions.FileTypeFilter.Add(".jpg");
queryOptions.FileTypeFilter.Add(".bmp");
var query = folder.CreateFileQueryWithOptions(queryOptions);
var files = await query.GetFilesAsync();
ImagePath img;
imgList = new ObservableCollection<ImagePath>();
foreach (var item in files)
{
IRandomAccessStream irandom = await item.OpenAsync(FileAccessMode.Read);
//对图像源使用流源
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.DecodePixelWidth = 160;
bitmapImage.DecodePixelHeight = 100;
await bitmapImage.SetSourceAsync(irandom);
img = new ImagePath();
img.Path = item.Path;
img.File = bitmapImage;
img.Storage = item;
imgList.Add(img);
}
imageView.ItemsSource = imgList;
}
}
}
}
catch (Exception exce)
{
await new MessageDialog(exce.ToString()).ShowAsync();
throw exce;
}
}

效果图
avatar

播放页面

4-design3

回看标签

这个之前已经讲过,定制过的listbox就是解决方案,只不过把水平方向换成垂直方向
当然,如果可以我还是想弄成等腰梯形(

回看节目栏

StackPanel就可以搞定,当然还需要定制一下ui,但是这个回看栏的翻页方式,目前我们还没有决定,是继续沿用纵向滚动的方式来加载往期节目(当然,只是滚动太low了,如果是我我也许会考虑使用异步加载来增加用户体验,用动画来掩盖加载慢的事实,用来让用户产生好流畅好惊艳的错觉,就像苹果公司经常干的。。。。。辱果了,异教徒!处死!)
还可以在stackpanel最后加入两个按钮,用来横向翻页。。。这个也挺简单的,stackpanel套两个水平的button,当然button的图案上要使用箭头作为图标,具体怎么加图标嘛
https://stackoverflow.com/questions/41191758/uwp-xaml-how-to-display-a-button-with-icon-and-text-in-it
这个老哥的解释和代码我们可以看看

1
2
3
4
5
6
<Button>
<StackPanel>
<Image/>
<TextBlock/>
</StackPanel>
</Button>

这样可以让button上面有多重存在了

1
<Image Width="200" Source="Images/myimage.png"/>

然后像这样设置一下icon就好了,icon的来源嘛
百度呀,谷歌呀,一大堆

p.s. 补充,这里回看节目单的效果个人认为也可以做一个ListView的列表滑动特效,不考虑加载速度etc.就是觉得这样的动态效果比较活泼。实现可以参照一篇博客

弹幕/评论区

这种很简单的文本堆叠区域用stack panel + text block很容易实现

弹幕/评论输入栏

TextBox就可以了,提交给后端,后端存到相对应的数据库

弹幕/评论发送按钮

一个button就可以了,设置一下onclick,上面的文本和图标已经提到过了

视频播放器

1
2
3
4
5
6
7
<MediaPlayerElement
Name="MediaPlayer" AutoPlay="False"
Grid.Row="0" Grid.Column="0"
Grid.RowSpan="3" Grid.ColumnSpan="3"
Visibility="Collapsed"
AreTransportControlsEnabled="True"
/>

之前已经写过类似的小作业了,很简单的调用一下api就好了
具体的代码可以看一下我的小作业
https://github.com/HallWoodZhang/SimpleMediaPlayer_UWP
注释
https://blog.csdn.net/hall_wood/article/details/79788564

(扩展)弹幕显示层

陷入江局.jpg
这个应该是最难的部分了。。。
这个问题的解决方案,应该是现有的,首先,我们应该明确一个问题,弹幕是不是可以实时更新?
如果可以实时更新,就像挂鱼直播那样的(呸),或者youtube那样的评论浮现方式,都不太好实现,可能涉及到一些websocket的实现(todo: extention # 1)

  • 那么,有没有一种方法可以偷懒呢?

干脆就不要实时更新弹幕了,那这就好解决了,每次打开视频,就加载某个danmaku(b站确实就叫这个)文件,然后用某种形式显示在媒体播放器上,每次有人发弹幕,就先存起来,然后刷新页面就可以看到更新的danmaku了
以前我用you-get爬某个大型同性交友网站视频的时候,爬到的弹幕库文件都是xml格式的,我们可以学习一个,约定好储存格式,能够适用于校园网用户规模的用户访问就行了

储存和发送的方法定好了,我们该如何显示弹幕,就是之后需要讨论的问题了,近年来有许多现成的实现代码,我们可以考虑参考一下他们的经验,然后自己改进一下

https://github.com/SkylarkWorkshop/SkylarkWsp.DanmakuEngine
ORZ已经有大佬实现了uwp弹幕库的引擎了,惊了!
看看接口学习一个,看看自己是否能DIY一下
真的是大佬吃肉,菜鸡喝汤就能够不饿死的时代啊,感谢上帝赐我大佬

目前我们还没有完全确定是否要加弹幕,毕竟工作量不小,但是网上(某hub)还是有实现弹幕视频网站的实例的
我们可以看看别人是怎么实现弹幕图层的
https://github.com/DaweiX/bilibili

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<UserControl
x:Class="bilibili.Controls.Danmaku"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:bilibili.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400"
SizeChanged="UserControl_SizeChanged">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="3*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid x:Name="grid_0">
<Grid.RowDefinitions>
<RowDefinition x:Name="r1"/>
<RowDefinition x:Name="r2"/>
<RowDefinition x:Name="r3"/>
<RowDefinition x:Name="r4"/>
<RowDefinition x:Name="r5"/>
<RowDefinition x:Name="r6"/>
<RowDefinition x:Name="r7"/>
<RowDefinition x:Name="r8"/>
<RowDefinition x:Name="r9"/>
<RowDefinition x:Name="r10"/>
<RowDefinition x:Name="space"/>
</Grid.RowDefinitions>
</Grid>
<Grid x:Name="grid_1" Grid.RowSpan="2">
<StackPanel x:Name="top" VerticalAlignment="Top"/>
<StackPanel x:Name="bottom" VerticalAlignment="Bottom"/>
</Grid>
</Grid>
</UserControl>

这位仁兄把一个空间上的grid分成了10行用来显示弹幕

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
using bilibili.Helpers;
using System;
using System.Threading.Tasks;
using Windows.UI;
using Windows.UI.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Animation;
// The User Control item template is documented at http://go.microsoft.com/fwlink/?LinkId=234236
namespace bilibili.Controls
{
public sealed partial class Danmaku : UserControl
{
int row = 0;
int speed = 8;
int fontsize = 21;
bool isPause = false;
string font = "默认";
public Danmaku()
{
this.InitializeComponent();
speed = SettingHelper.GetValue("_speed") != null ? int.Parse(SettingHelper.GetValue("_speed").ToString()) : 8;
if (SettingHelper.GetValue("_space") != null)
{
int value = int.Parse(SettingHelper.GetValue("_space").ToString());
ChangeSpace(value);
}
else
{
ChangeSpace(0);
}
}
/// <summary>
/// 添加滚动弹幕
/// </summary>
/// <param name="model"></param>
public async void AddBasic(DanmuModel model ,bool isMyDanmu)
{
TextBlock txt1 = new TextBlock
{
Foreground = model.DanColor,
FontFamily = new FontFamily(font)
};
TextBlock txt2 = new TextBlock
{
Foreground = new SolidColorBrush(Colors.Black),
FontFamily = new FontFamily(font),
Margin = new Thickness(1),
};
Grid grid = new Grid
{
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
DataContext = model,
};
if (isMyDanmu)
{
grid.BorderBrush = new SolidColorBrush(Colors.HotPink);
grid.BorderThickness = new Thickness(2);
}
double size = double.Parse(model.Size);
if (size == 25)
{
txt2.FontSize = fontsize;
txt1.FontSize = fontsize;
}
else
{
txt2.FontSize = fontsize - 4;
txt1.FontSize = fontsize - 4;
}
txt1.Text = txt2.Text = model.Message;
grid.Children.Add(txt2);
grid.Children.Add(txt1);
TranslateTransform move = new TranslateTransform();
move.X = grid_0.ActualWidth;
grid.RenderTransform = move;
grid_0.Children.Add(grid);
Grid.SetRow(grid, row);
row++;
if (row == 10)
row = 0;
grid.DataContext = model;
grid.UpdateLayout();
Storyboard sty = new Storyboard();
DoubleAnimation ani = new DoubleAnimation
{
Duration = new Duration(TimeSpan.FromSeconds(speed)),
To = -(grid.ActualWidth),
};
sty.Children.Add(ani);
Storyboard.SetTarget(ani, move);
Storyboard.SetTargetProperty(ani, "X");
grid.Resources.Remove("justintimeStoryboard");
grid.Resources.Add("sty", sty);
sty.Begin();
await Task.Run(async () =>
{
int i = 0;
while (true)
{
if (isPause)
{
await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => {
sty.Pause();
});
}
else
{
if (i == speed * 2)
{
break;
}
i++;
await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => {
sty.Resume();
});
}
await Task.Delay(500);
}
});
grid_0.Children.Remove(grid);
}
public void IsPauseDanmaku(bool isAction)
{
isPause = isAction;
}
public void Setfont(string fontname)
{
font = fontname;
}
/// <summary>
/// 弹幕间距
/// </summary>
public void ChangeSpace(int value)
{
double h = 0;
double actualHeight = r1.ActualHeight;
switch (value)
{
case 0:
h = 2;break;
case 1:
h = 4;break;
case 2:
h = 8;break;
}
space.Height = new GridLength(h, GridUnitType.Pixel);
}
/// <summary>
/// 弹幕速度
/// </summary>
/// <param name="value"></param>
public void ChangeSpeed(int value)
{
speed = value;
}
/// <summary>
/// 弹幕字号
/// </summary>
/// <param name="value"></param>
public void ChangeSize(int value)
{
switch(value)
{
case 0:fontsize = 14;break;
case 1: fontsize = 16; break;
case 2: fontsize = 21; break;
case 3: fontsize = 25; break;
}
}
/// <summary>
/// 添加顶部和底部弹幕
/// </summary>
/// <param name="model"></param>
public async void AddTop(DanmuModel model, bool isMyDanmu)
{
TextBlock txt1 = new TextBlock
{
Foreground = model.DanColor,
FontFamily = new FontFamily(font),
};
TextBlock txt2 = new TextBlock
{
Foreground = new SolidColorBrush(Colors.Black),
Margin = new Thickness(1),
FontFamily = new FontFamily(font),
};
Grid grid = new Grid
{
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Top,
DataContext = model,
};
if (isMyDanmu)
{
grid.BorderBrush = new SolidColorBrush(Colors.HotPink);
grid.BorderThickness = new Thickness(2);
}
double size = double.Parse(model.Size);
if (size == 25)
{
txt2.FontSize = fontsize;
txt1.FontSize = fontsize;
}
else
{
txt2.FontSize = fontsize - 4;
txt1.FontSize = fontsize - 4;
}
txt1.Text = txt2.Text = model.Message;
grid.Children.Add(txt2);
grid.Children.Add(txt1);
grid.UpdateLayout();
if (model.Mode == "5")
{
top.Children.Add(grid);
await Task.Delay(5000);
if (!isPause)
{
top.Children.Remove(grid);
}
}
else if (model.Mode == "4")
{
bottom.Children.Add(grid);
await Task.Delay(5000);
if (!isPause)
{
bottom.Children.Remove(grid);
}
}
}
/// <summary>
/// 清除弹幕
/// </summary>
public void ClearDanmu()
{
grid_0.Children.Clear();
top.Children.Clear();
bottom.Children.Clear();
}
/// <summary>
/// 清除顶部和底部
/// </summary>
public void ClearStaticDanmu()
{
top.Children.Clear();
bottom.Children.Clear();
}
public void ClearGun(bool isvisual)
{
grid_0.Visibility = isvisual ? Visibility.Visible : Visibility.Collapsed;
}
public void ClearBottom(bool isvisual)
{
bottom.Visibility = isvisual ? Visibility.Visible : Visibility.Collapsed;
}
public void ClearTop(bool isvisual)
{
top.Visibility = isvisual ? Visibility.Visible : Visibility.Collapsed;
}
public class DanmuModel
{
public string aid { get; set; }
public string Message { get; set; }
private decimal time;
public decimal Time
{
get { return time; }
set { time = value; }
}
/// <summary>
/// 3 滚动弹幕 4底端弹幕 5顶端弹幕 6.逆向弹幕 7精准定位 8高级弹幕
/// </summary>
public string Mode { get; set; }
public string Size { get; set;}
public string Color { get; set; }
public SolidColorBrush color { get; set; }
public SolidColorBrush DanColor
{
get
{
try
{
Color = Convert.ToInt32(Color).ToString("X2");
if (Color.StartsWith("#"))
Color = Color.Replace("#", string.Empty);
int v = int.Parse(Color, System.Globalization.NumberStyles.HexNumber);
SolidColorBrush solid = new SolidColorBrush(new Color()
{
A = Convert.ToByte(255),
R = Convert.ToByte((v >> 16) & 255),
G = Convert.ToByte((v >> 8) & 255),
B = Convert.ToByte((v >> 0) & 255)
});
color = solid;
return solid;
}
catch (Exception)
{
SolidColorBrush solid = new SolidColorBrush(new Color()
{
A = 255,
R = 255,
G = 255,
B = 255
});
color = solid;
return solid;
}
}
}
}
private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (ActualHeight < 500)
{
fontsize = 16;
}
else
{
fontsize = 21;
}
}
}
}

这个是这位仁兄的后端代码,可以说是十分规整,解决了我们如何显示弹幕在某一控件上的问题

那么问题来了,我们如何把这个控件和微软自带的媒体播放器接合在一起呢?
这需要我们进一步讨论解决方案,是采用bilibili uwp端的代码改装呢,还是使用现有的弹幕引擎,这仍然待定

毕竟,我们有可能压根就不实现这个功能。。。。

总结

只做了一点微小的工作很惭愧。队里氛围很好哇,dalao们都非常可爱。只是一讨论就会给自己不断挖坑,目前新增一坑缓存视频功能。
软件工程还是很重要的,避免疯狂堆加功能,开发模型有利于把握开发进度。还是要学习一个。