From ade5c85eed8b67e70a3ba9bbab532d58928d7ca5 Mon Sep 17 00:00:00 2001 From: Michael Gordeev Date: Thu, 9 May 2019 21:39:10 +0300 Subject: [PATCH] [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 --- FoxTube/Assets/Data/Patchnotes.xml | 8 +- FoxTube/Classes/ManifestGenerator.cs | 118 ++++++++++++++++++++ FoxTube/Classes/Methods.cs | 6 +- FoxTube/Classes/VideoProcessor.cs | 69 ------------ FoxTube/Controls/LiveCaptions.xaml.cs | 5 +- FoxTube/Controls/Player/PlayerControls.cs | 6 +- FoxTube/Controls/Player/VideoPlayer.xaml | 8 +- FoxTube/Controls/Player/VideoPlayer.xaml.cs | 93 ++++++++------- FoxTube/FoxTube.csproj | 2 +- FoxTube/Pages/MainPage.xaml | 35 ------ FoxTube/Pages/MainPage.xaml.cs | 2 +- 11 files changed, 197 insertions(+), 155 deletions(-) create mode 100644 FoxTube/Classes/ManifestGenerator.cs delete mode 100644 FoxTube/Classes/VideoProcessor.cs diff --git a/FoxTube/Assets/Data/Patchnotes.xml b/FoxTube/Assets/Data/Patchnotes.xml index b9ee5f3..9c72cdf 100644 --- a/FoxTube/Assets/Data/Patchnotes.xml +++ b/FoxTube/Assets/Data/Patchnotes.xml @@ -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 ### Что нового: - Исправлена проблема получения истории, "Посмотреть позже" и рекомендаций - Исправлен внешний вид рекламы - Исправлено появление водяных занков рекламы на видео при открытии через уведомления -- Исправлена загрузка видео +- Оптимизирован и улучшен просмотр видео - Исправлено появление особых символов в уведомлениях - Редизайн страницы истории - Добавлено управление историей просмотра приложения (не влияет на историю просмотров на сайте) @@ -33,6 +35,8 @@ - Добавлен режим инкогнито (доступен в контекстном меню видео карточки) - Подсказки при поиске работают плавнее - Теперь на кнопке отключения рекламы отображается текущая цена +- Исправлены вылеты при попытке открыть ссылку не содержащую http(s) префикс +- Исправлена обратная навигация при уменьшенном видео diff --git a/FoxTube/Classes/ManifestGenerator.cs b/FoxTube/Classes/ManifestGenerator.cs new file mode 100644 index 0000000..53f5641 --- /dev/null +++ b/FoxTube/Classes/ManifestGenerator.cs @@ -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 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 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"; + } + } + } +} diff --git a/FoxTube/Classes/Methods.cs b/FoxTube/Classes/Methods.cs index 559aa29..32a5349 100644 --- a/FoxTube/Classes/Methods.cs +++ b/FoxTube/Classes/Methods.cs @@ -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); } diff --git a/FoxTube/Classes/VideoProcessor.cs b/FoxTube/Classes/VideoProcessor.cs deleted file mode 100644 index c5a976e..0000000 --- a/FoxTube/Classes/VideoProcessor.cs +++ /dev/null @@ -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 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(); - } - - - } -} diff --git a/FoxTube/Controls/LiveCaptions.xaml.cs b/FoxTube/Controls/LiveCaptions.xaml.cs index 108213c..734d2cb 100644 --- a/FoxTube/Controls/LiveCaptions.xaml.cs +++ b/FoxTube/Controls/LiveCaptions.xaml.cs @@ -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; diff --git a/FoxTube/Controls/Player/PlayerControls.cs b/FoxTube/Controls/Player/PlayerControls.cs index 5d249d5..47ca80e 100644 --- a/FoxTube/Controls/Player/PlayerControls.cs +++ b/FoxTube/Controls/Player/PlayerControls.cs @@ -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 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()); } } } diff --git a/FoxTube/Controls/Player/VideoPlayer.xaml b/FoxTube/Controls/Player/VideoPlayer.xaml index fd0fc73..d0849ca 100644 --- a/FoxTube/Controls/Player/VideoPlayer.xaml +++ b/FoxTube/Controls/Player/VideoPlayer.xaml @@ -11,13 +11,13 @@ RequestedTheme="Dark"> - - + + - - + + diff --git a/FoxTube/Controls/Player/VideoPlayer.xaml.cs b/FoxTube/Controls/Player/VideoPlayer.xaml.cs index 7a823a9..63c2ea0 100644 --- a/FoxTube/Controls/Player/VideoPlayer.xaml.cs +++ b/FoxTube/Controls/Player/VideoPlayer.xaml.cs @@ -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) diff --git a/FoxTube/FoxTube.csproj b/FoxTube/FoxTube.csproj index 71a18b3..154879a 100644 --- a/FoxTube/FoxTube.csproj +++ b/FoxTube/FoxTube.csproj @@ -107,7 +107,7 @@ - + Advert.xaml diff --git a/FoxTube/Pages/MainPage.xaml b/FoxTube/Pages/MainPage.xaml index 2c69d66..42e31fd 100644 --- a/FoxTube/Pages/MainPage.xaml +++ b/FoxTube/Pages/MainPage.xaml @@ -120,47 +120,12 @@ - - - diff --git a/FoxTube/Pages/MainPage.xaml.cs b/FoxTube/Pages/MainPage.xaml.cs index 1fa2b8d..90ecc1b 100644 --- a/FoxTube/Pages/MainPage.xaml.cs +++ b/FoxTube/Pages/MainPage.xaml.cs @@ -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();