Archived
1
0

[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
This commit is contained in:
Michael Gordeev
2019-05-09 21:39:10 +03:00
parent 5eb56f2335
commit ade5c85eed
11 changed files with 197 additions and 155 deletions
+6 -2
View File
@@ -6,7 +6,7 @@
- Fixed fails when trying to retrieve history, WL or recommended
- Fixed ads appearance
- Fixed ads watermarks on video when it was opened through notification
- Fixed videos loading
- 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)
@@ -17,12 +17,14 @@
- 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
</en-US>
<ru-RU>### Что нового:
- Исправлена проблема получения истории, "Посмотреть позже" и рекомендаций
- Исправлен внешний вид рекламы
- Исправлено появление водяных занков рекламы на видео при открытии через уведомления
- Исправлена загрузка видео
- Оптимизирован и улучшен просмотр видео
- Исправлено появление особых символов в уведомлениях
- Редизайн страницы истории
- Добавлено управление историей просмотра приложения (не влияет на историю просмотров на сайте)
@@ -33,6 +35,8 @@
- Добавлен режим инкогнито (доступен в контекстном меню видео карточки)
- Подсказки при поиске работают плавнее
- Теперь на кнопке отключения рекламы отображается текущая цена
- Исправлены вылеты при попытке открыть ссылку не содержащую http(s) префикс
- Исправлена обратная навигация при уменьшенном видео
</ru-RU>
</content>
</item>
+118
View File
@@ -0,0 +1,118 @@
using System;
using System.Threading.Tasks;
using YoutubeExplode.Models.MediaStreams;
using Windows.Storage;
using Google.Apis.YouTube.v3.Data;
using System.Xml;
using System.IO;
using System.Linq;
using System.Collections.Generic;
namespace FoxTube.Controls.Player
{
public static class ManifestGenerator
{
static StorageFolder roaming = ApplicationData.Current.RoamingFolder;
public static async Task<Uri> GetManifest(Video meta, VideoStreamInfo requestedQuality, MediaStreamInfoSet list)
{
StorageFile manifest = await roaming.CreateFileAsync("manifest.mpd", CreationCollisionOption.ReplaceExisting);
XmlDocument doc = new XmlDocument();
XmlElement mpd = doc.CreateElement("MPD");
mpd.SetAttribute("mediaPresentationDuration", meta.ContentDetails.Duration);
XmlElement period = doc.CreateElement("Period");
period.SetAttribute("duration", meta.ContentDetails.Duration);
period.SetAttribute("start", "PT0S");
XmlElement videoSet = doc.CreateElement("AdaptationSet");
XmlElement videoMeta = doc.CreateElement("ContentComponent");
videoMeta.SetAttribute("contentType", "video");
videoSet.AppendChild(videoMeta);
AppendVideoSet(doc, videoSet, list.Video);
XmlElement audioSet = doc.CreateElement("AdaptationSet");
XmlElement audioMeta = doc.CreateElement("ContentComponent");
audioMeta.SetAttribute("contentType", "audio");
audioSet.AppendChild(audioMeta);
XmlElement audio = doc.CreateElement("Representation");
audio.SetAttribute("bandwidth", "100000");
audio.SetAttribute("id", (list.Video.Count + 1).ToString());
audio.SetAttribute("mimeType", $"audio/{list.Audio.First().Container.GetFileExtension()}");
XmlElement audioUrl = doc.CreateElement("BaseURL");
audioUrl.InnerText = list.Audio.First().Url;
audio.AppendChild(audioUrl);
audioSet.AppendChild(audio);
doc.AppendChild(mpd);
mpd.AppendChild(period);
period.AppendChild(videoSet);
period.AppendChild(audioSet);
doc.Save(await manifest.OpenStreamForWriteAsync());
return "ms-appdata:///roaming/manifest.mpd".ToUri();
}
private static void AppendVideoSet(XmlDocument doc, XmlElement root, IReadOnlyList<VideoStreamInfo> list)
{
for (int k = 0; k < list.Count; k++)
{
XmlElement representation = doc.CreateElement("Representation");
representation.SetAttribute("bandwidth", GetBandwidth(list[k].VideoQuality));
representation.SetAttribute("height", list[k].Resolution.Height.ToString());
representation.SetAttribute("width", list[k].Resolution.Width.ToString());
representation.SetAttribute("id", (k + 1).ToString());
representation.SetAttribute("mimeType", $"video/{list[k].Container.GetFileExtension()}");
XmlElement baseUrl = doc.CreateElement("BaseURL");
baseUrl.InnerText = list[k].Url;
representation.AppendChild(baseUrl);
XmlElement segmentBase = doc.CreateElement("SegmentBase");
representation.AppendChild(segmentBase);
XmlElement initialization = doc.CreateElement("Initialization");
initialization.SetAttribute("range", "0-1000");
segmentBase.AppendChild(initialization);
root.AppendChild(representation);
}
}
private static string GetBandwidth(VideoQuality quality)
{
switch(quality)
{
case VideoQuality.High4320:
return $"16763040";
case VideoQuality.High3072:
return $"11920384";
case VideoQuality.High2880:
return $"11175360";
case VideoQuality.High2160:
return $"8381520";
case VideoQuality.High1440:
return $"5587680";
case VideoQuality.High1080:
return $"4190760";
case VideoQuality.High720:
return $"2073921";
case VideoQuality.Medium480:
return $"869460";
case VideoQuality.Medium360:
return $"686521";
case VideoQuality.Low240:
return $"264835";
case VideoQuality.Low144:
default:
return $"100000";
}
}
}
}
+5 -1
View File
@@ -214,8 +214,12 @@ namespace FoxTube
{
if (link.IsMatch(item))
{
string str = item;
if (!str.Contains("http"))
str = str.Insert(0, "http://");
Hyperlink hl = new Hyperlink();
hl.Click += (s, arg) => ProcessLink(item);
hl.Click += (s, arg) => ProcessLink(str);
hl.Inlines.Add(new Run { Text = item });
block.Inlines.Add(hl);
}
-69
View File
@@ -1,69 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using YoutubeExplode.Models.MediaStreams;
using Windows.Media.Editing;
using Windows.Media.Core;
using Windows.Storage;
using Windows.UI.Xaml.Controls;
using System.Net;
using YoutubeExplode;
namespace FoxTube.Classes
{
public class VideoProcessor
{
public MediaElement Player { get; set; }
MediaComposition composition = new MediaComposition();
StorageFolder roaming = ApplicationData.Current.RoamingFolder;
StorageFile audioCache;
StorageFile videoCache;
MediaStream videoStream;
MediaStream audioStream;
YoutubeClient client = new YoutubeClient(SecretsVault.HttpClient);
public VideoProcessor()
{
//Player.CurrentStateChanged += Player_CurrentStateChanged;
}
public async Task<MediaStreamSource> GetStream(VideoStreamInfo video, AudioStreamInfo audio)
{
audioCache = await roaming.CreateFileAsync("audioCache.mp4", CreationCollisionOption.ReplaceExisting);
videoCache = await roaming.CreateFileAsync("videoCache.mp4", CreationCollisionOption.ReplaceExisting);
videoStream = await client.GetMediaStreamAsync(video);
audioStream = await client.GetMediaStreamAsync(audio);
/*Stream write = await videoCache.OpenStreamForWriteAsync();
write.
write.WriteAsync()*/
videoStream.BeginRead(new byte[300 * video.Bitrate], (int)((int)Player.Position.TotalSeconds * video.Bitrate), (int)((int)Player.Position.Add(TimeSpan.FromMinutes(5)).TotalSeconds * video.Bitrate - (int)Player.Position.TotalSeconds * video.Bitrate), null, null);
composition.BackgroundAudioTracks.Add(await BackgroundAudioTrack.CreateFromFileAsync(audioCache));
composition.Clips.Add(await MediaClip.CreateFromFileAsync(videoCache));
return composition.GenerateMediaStreamSource();
}
public async void Close()
{
await audioCache.DeleteAsync(StorageDeleteOption.PermanentDelete);
await videoCache.DeleteAsync(StorageDeleteOption.PermanentDelete);
}
private void Player_CurrentStateChanged(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
throw new NotImplementedException();
}
}
}
+3 -2
View File
@@ -1,4 +1,5 @@
using System;
using Windows.Media.Playback;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using YoutubeExplode.Models.ClosedCaptions;
@@ -16,7 +17,7 @@ namespace FoxTube.Controls
set => text.FontSize = value;
}
public MediaElement Player { get; set; }
public MediaPlayer Player { get; set; }
DispatcherTimer timer = new DispatcherTimer() { Interval = TimeSpan.FromMilliseconds(10) };
ClosedCaption currentCaption = null;
@@ -31,7 +32,7 @@ namespace FoxTube.Controls
private void UpdateCaption(object sender, object e)
{
currentCaption = track.Captions.Find(i => i.Offset <= Player.Position && i.Offset + i.Duration > Player.Position);
currentCaption = track.Captions.Find(i => i.Offset <= Player.PlaybackSession.Position && i.Offset + i.Duration > Player.PlaybackSession.Position);
if (currentCaption != null)
text.Text = currentCaption.Text;
+4 -2
View File
@@ -5,6 +5,8 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Windows.ApplicationModel.Resources;
using Windows.Media.Core;
using Windows.Media.Playback;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
@@ -29,7 +31,7 @@ namespace FoxTube
public event QualityChangedEventHandler QualityChanged;
public MediaElement Player;
public MediaPlayer Player;
public PlayerAdvert Advert;
public IReadOnlyList<ClosedCaptionTrackInfo> ClosedCaptions { get; set; }
@@ -347,7 +349,7 @@ namespace FoxTube
(GetTemplateChild("cc") as Button).Visibility = Visibility.Collapsed;
(GetTemplateChild("quality") as Button).Visibility = Visibility.Collapsed;
Player.Source = url.ToUri();
Player.Source = MediaSource.CreateFromUri(url.ToUri());
}
}
}
+4 -4
View File
@@ -11,13 +11,13 @@
RequestedTheme="Dark">
<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.TransportControls>
<MediaPlayerElement Name="videoSource" AreTransportControlsEnabled="True" PosterSource="ms-appx:///Assets/videoThumbSample.png">
<MediaPlayerElement.TransportControls>
<foxtube:PlayerControls IsCompactOverlayButtonVisible="True" IsCompactOverlayEnabled="True"
IsFullWindowButtonVisible="True" IsFullWindowEnabled="True"
IsSkipBackwardButtonVisible="True" IsSkipBackwardEnabled="True"
IsSkipForwardButtonVisible="True" IsSkipForwardEnabled="True"/>
</MediaElement.TransportControls>
</MediaElement>
</MediaPlayerElement.TransportControls>
</MediaPlayerElement>
</Grid>
</UserControl>
+55 -38
View File
@@ -11,6 +11,14 @@ 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;
namespace FoxTube
{
@@ -23,17 +31,17 @@ namespace FoxTube
public event Event NextClicked;
public event ObjectEventHandler MiniMode;
public PlayerControls Controls => videoSource.TransportControls as PlayerControls;
public MediaPlayer Player { get; } = new MediaPlayer();
public TimeSpan Position
{
get { return videoSource.Position; }
set { videoSource.Position = value; }
get { return videoSource.MediaPlayer.PlaybackSession.Position; }
set { videoSource.MediaPlayer.PlaybackSession.Position = value; }
}
VideoProcessor processor;
TimeSpan timecodeBackup;
bool needUpdateTimecode = false;
//TimeSpan timecodeBackup;
//bool needUpdateTimecode = false;
SystemMediaTransportControls systemControls;
@@ -58,11 +66,12 @@ namespace FoxTube
if (item.Snippet.LiveBroadcastContent == "none")
{
InitializeContols();
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) });
// TODO: make ads live again
/*if (Methods.GetDuration(item.ContentDetails.Duration).TotalMinutes > 5)
Player.PlaybackSession.Add(new Windows.UI.Xaml.Media.TimelineMarker { Time = Methods.GetDuration(item.ContentDetails.Duration) - TimeSpan.FromMinutes(1) });
if(Methods.GetDuration(item.ContentDetails.Duration).TotalMinutes >= 60)
for (int k = 1; k < Methods.GetDuration(item.ContentDetails.Duration).TotalMinutes / 30; k++)
videoSource.Markers.Add(new Windows.UI.Xaml.Media.TimelineMarker { Time = TimeSpan.FromMinutes(k * 30) });
videoSource.Markers.Add(new Windows.UI.Xaml.Media.TimelineMarker { Time = TimeSpan.FromMinutes(k * 30) });*/
Controls.SetQualities(await new YoutubeClient().GetVideoMediaStreamInfosAsync(item.Id));
Controls.SetCaptions(await new YoutubeClient().GetVideoClosedCaptionTrackInfosAsync(item.Id));
@@ -87,22 +96,23 @@ namespace FoxTube
private void Controls_LiveRequested(object sender, RoutedEventArgs e)
{
videoSource.Position = videoSource.NaturalDuration.TimeSpan;
Position = Player.PlaybackSession.NaturalDuration;
}
public void InitializeContols()
{
processor = new VideoProcessor
{
Player = videoSource
};
videoSource.Volume = SettingsStorage.Volume;
videoSource.SetMediaPlayer(Player);
Player.Volume = SettingsStorage.Volume;
Player.CurrentStateChanged += VideoSource_CurrentStateChanged;
Player.MediaOpened += VideoSource_MediaOpened;
Player.VolumeChanged += VideoSource_VolumeChanged;
Controls.CloseRequested += Controls_CloseRequested;
Controls.NextRequested += (s, e) => NextClicked?.Invoke();
Controls.QualityChanged += Controls_QualityChanged;
Controls.MiniModeChanged += Controls_MiniModeChanged;
Controls.Player = videoSource;
Controls.Player = Player;
#region System Media Transport Controls
systemControls = SystemMediaTransportControls.GetForCurrentView();
@@ -135,16 +145,23 @@ namespace FoxTube
private async void Controls_QualityChanged(object sender, MediaStreamInfo requestedQuality, MediaStreamInfoSet list)
{
videoSource.Pause();
Player.Pause();
timecodeBackup = videoSource.Position;
Player.Source = MediaSource.CreateFromUri(await ManifestGenerator.GetManifest(item, requestedQuality as VideoStreamInfo, list));
//await ManifestGenerator.GetManifest(item, requestedQuality as VideoStreamInfo, list);
/*FileOpenPicker picker = new FileOpenPicker();
picker.FileTypeFilter.Add(".mpd");*/
//Player.Source = MediaSource.CreateFromUri("https://foxsharp.000webhostapp.com/dash_sample.mpd".ToUri());
/*timecodeBackup = videoSource.Position;
needUpdateTimecode = true;
if (requestedQuality is MuxedStreamInfo)
videoSource.Source = requestedQuality.Url.ToUri();
else
videoSource.SetPlaybackSource(MediaSource.CreateFromStream((await new YoutubeClient().GetMediaStreamAsync(requestedQuality)).AsRandomAccessStream(), "video"));
//videoSource.SetMediaStreamSource(await processor.GetStream(requestedQuality as VideoStreamInfo, list.Audio.First()));
processor.Start(requestedQuality as VideoStreamInfo, list);*/
}
public void Controls_CloseRequested(object sender, RoutedEventArgs e)
@@ -154,12 +171,10 @@ namespace FoxTube
if (!incognito)
{
history.LeftOn = videoSource.Position;
history.LeftOn = Player.PlaybackSession.Position;
HistorySet.Update(history);
}
videoSource.Stop();
Methods.MainPage.CloseVideo();
}
@@ -170,10 +185,10 @@ namespace FoxTube
switch (args.Button)
{
case SystemMediaTransportControlsButton.Play:
videoSource.Play();
Player.Play();
break;
case SystemMediaTransportControlsButton.Pause:
videoSource.Pause();
Player.Pause();
break;
case SystemMediaTransportControlsButton.Next:
NextClicked?.Invoke();
@@ -184,40 +199,42 @@ namespace FoxTube
public void Pause()
{
videoSource.Pause();
Player.Pause();
}
private void VideoSource_CurrentStateChanged(object sender, RoutedEventArgs e)
private void VideoSource_CurrentStateChanged(MediaPlayer sender, object e)
{
switch(videoSource.CurrentState)
switch (Player.PlaybackSession.PlaybackState)
{
case Windows.UI.Xaml.Media.MediaElementState.Buffering:
case Windows.UI.Xaml.Media.MediaElementState.Paused:
case MediaPlaybackState.Buffering:
case MediaPlaybackState.None:
case MediaPlaybackState.Opening:
case MediaPlaybackState.Paused:
systemControls.PlaybackStatus = MediaPlaybackStatus.Paused;
if(!incognito)
if (!incognito)
{
history.LeftOn = videoSource.Position;
history.LeftOn = Player.PlaybackSession.Position;
HistorySet.Update(history);
}
break;
case Windows.UI.Xaml.Media.MediaElementState.Playing:
case MediaPlaybackState.Playing:
systemControls.PlaybackStatus = MediaPlaybackStatus.Playing;
break;
}
}
private void VideoSource_MediaOpened(object sender, RoutedEventArgs e)
private void VideoSource_MediaOpened(MediaPlayer sender, object e)
{
if (!needUpdateTimecode)
/*if (!needUpdateTimecode)
return;
videoSource.Position = timecodeBackup;
needUpdateTimecode = false;
needUpdateTimecode = false;*/
}
private void VideoSource_VolumeChanged(object sender, RoutedEventArgs e)
private void VideoSource_VolumeChanged(MediaPlayer sender, object e)
{
SettingsStorage.Volume = videoSource.Volume;
SettingsStorage.Volume = Player.Volume;
}
private void VideoSource_MarkerReached(object sender, Windows.UI.Xaml.Media.TimelineMarkerRoutedEventArgs e)
+1 -1
View File
@@ -107,7 +107,7 @@
<Compile Include="Classes\Methods.cs" />
<Compile Include="Classes\SearchPaameters.cs" />
<Compile Include="Classes\SettingsStorage.cs" />
<Compile Include="Classes\VideoProcessor.cs" />
<Compile Include="Classes\ManifestGenerator.cs" />
<Compile Include="Controls\Advert.xaml.cs">
<DependentUpon>Advert.xaml</DependentUpon>
</Compile>
-35
View File
@@ -120,47 +120,12 @@
</TransitionCollection>
</ui:NavigationViewList.ItemContainerTransitions>
<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 Content="Remove ads" Visibility="Collapsed" Tapped="RemoveAds_Tapped" Name="removeAds">
<ui:NavigationViewItem.Icon>
<FontIcon Glyph="&#xE14D;"/>
</ui:NavigationViewItem.Icon>
</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:NavigationView.PaneFooter>
+1 -1
View File
@@ -591,7 +591,7 @@ namespace FoxTube
private void Nav_BackRequested(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewBackRequestedEventArgs args)
{
if (videoPlaceholder.Content != null)
if (videoPlaceholder.Content != null && videoPlaceholder.Width == double.NaN)
{
if ((videoPlaceholder.Content as VideoPage).LoadingPage.State != LoadingState.Loaded)
CloseVideo();