- Fixed manifest generator
- Added "Auto" quality
This commit is contained in:
@@ -21,6 +21,7 @@
|
|||||||
- Fixed backward navigation with minimized video
|
- Fixed backward navigation with minimized video
|
||||||
- Player re-design
|
- Player re-design
|
||||||
- Added quality selector to live streams playback
|
- Added quality selector to live streams playback
|
||||||
|
- Added "Auto" quality option for videos
|
||||||
</en-US>
|
</en-US>
|
||||||
<ru-RU>### Что нового:
|
<ru-RU>### Что нового:
|
||||||
- Исправлена проблема получения истории, "Посмотреть позже" и рекомендаций
|
- Исправлена проблема получения истории, "Посмотреть позже" и рекомендаций
|
||||||
@@ -41,6 +42,7 @@
|
|||||||
- Исправлена обратная навигация при уменьшенном видео
|
- Исправлена обратная навигация при уменьшенном видео
|
||||||
- Редизайн плеера
|
- Редизайн плеера
|
||||||
- Добавлено меню выбора качества для прямых эфиров
|
- Добавлено меню выбора качества для прямых эфиров
|
||||||
|
- Добавлено опция "Авто" в меню выбора качеста видео
|
||||||
</ru-RU>
|
</ru-RU>
|
||||||
</content>
|
</content>
|
||||||
</item>
|
</item>
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ using System.Net;
|
|||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Web;
|
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
|
using Windows.ApplicationModel.Resources;
|
||||||
using Windows.Storage;
|
using Windows.Storage;
|
||||||
using YoutubeExplode.Models.MediaStreams;
|
using YoutubeExplode.Models.MediaStreams;
|
||||||
|
|
||||||
@@ -31,119 +31,164 @@ namespace FoxTube.Controls.Player
|
|||||||
{
|
{
|
||||||
manifest = await roaming.CreateFileAsync("manifest.mpd", CreationCollisionOption.GenerateUniqueName);
|
manifest = await roaming.CreateFileAsync("manifest.mpd", CreationCollisionOption.GenerateUniqueName);
|
||||||
}
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
|
||||||
XmlDocument doc = new XmlDocument();
|
XmlDocument doc = new XmlDocument();
|
||||||
|
|
||||||
XmlElement mpd = doc.CreateElement("MPD");
|
XmlElement mpd = doc.CreateElement("MPD");
|
||||||
mpd.SetAttribute("mediaPresentationDuration", meta.ContentDetails.Duration);
|
mpd.SetAttribute("mediaPresentationDuration", meta.ContentDetails.Duration);
|
||||||
mpd.SetAttribute("minBufferTime", "PT2S");
|
mpd.SetAttribute("minBufferTime", "PT2S");
|
||||||
mpd.SetAttribute("profiles", "urn:mpeg:dash:profile:isoff-on-demand:2011");
|
mpd.SetAttribute("profiles", "urn:mpeg:dash:profile:isoff-on-demand:2011");
|
||||||
mpd.SetAttribute("type", "static");
|
mpd.SetAttribute("type", "static");
|
||||||
|
|
||||||
XmlElement period = doc.CreateElement("Period");
|
XmlElement period = doc.CreateElement("Period");
|
||||||
|
|
||||||
XmlElement videoSet = doc.CreateElement("AdaptationSet");
|
XmlElement videoSet = doc.CreateElement("AdaptationSet");
|
||||||
XmlElement videoMeta = doc.CreateElement("ContentComponent");
|
XmlElement videoMeta = doc.CreateElement("ContentComponent");
|
||||||
videoMeta.SetAttribute("contentType", "video");
|
videoMeta.SetAttribute("contentType", "video");
|
||||||
videoMeta.SetAttribute("id", "1");
|
videoMeta.SetAttribute("id", "1");
|
||||||
videoSet.AppendChild(videoMeta);
|
videoSet.AppendChild(videoMeta);
|
||||||
|
|
||||||
StreamInfo streamInfo = await GetInfoAsync(meta, requestedQuality);
|
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");
|
XmlElement representation = doc.CreateElement("Representation");
|
||||||
representation.SetAttribute("bandwidth", GetBandwidth(requestedQuality.VideoQuality));
|
representation.SetAttribute("bandwidth", GetBandwidth(info.Label));
|
||||||
representation.SetAttribute("id", "1");
|
representation.SetAttribute("id", info.Itag);
|
||||||
representation.SetAttribute("mimeType", $"video/{requestedQuality.Container.GetFileExtension()}");
|
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");
|
XmlElement baseUrl = doc.CreateElement("BaseURL");
|
||||||
baseUrl.InnerText = requestedQuality.Url;
|
baseUrl.InnerText = info.Url;
|
||||||
representation.AppendChild(baseUrl);
|
representation.AppendChild(baseUrl);
|
||||||
|
|
||||||
XmlElement segmentBase = doc.CreateElement("SegmentBase");
|
XmlElement segmentBase = doc.CreateElement("SegmentBase");
|
||||||
segmentBase.SetAttribute("indexRange", streamInfo.Video.IndexRange);
|
segmentBase.SetAttribute("indexRange", info.IndexRange);
|
||||||
representation.AppendChild(segmentBase);
|
representation.AppendChild(segmentBase);
|
||||||
|
|
||||||
XmlElement initialization = doc.CreateElement("Initialization");
|
XmlElement initialization = doc.CreateElement("Initialization");
|
||||||
initialization.SetAttribute("range", streamInfo.Video.InitRange);
|
initialization.SetAttribute("range", info.InitRange);
|
||||||
segmentBase.AppendChild(initialization);
|
segmentBase.AppendChild(initialization);
|
||||||
|
|
||||||
videoSet.AppendChild(representation);
|
return representation;
|
||||||
|
}
|
||||||
XmlElement audioSet = doc.CreateElement("AdaptationSet");
|
|
||||||
XmlElement audioMeta = doc.CreateElement("ContentComponent");
|
|
||||||
audioMeta.SetAttribute("contentType", "audio");
|
|
||||||
audioMeta.SetAttribute("id", "2");
|
|
||||||
audioSet.AppendChild(audioMeta);
|
|
||||||
|
|
||||||
|
private static XmlElement GetAudioPresentation(XmlDocument doc, StreamInfo.AudioInfo info)
|
||||||
|
{
|
||||||
XmlElement audio = doc.CreateElement("Representation");
|
XmlElement audio = doc.CreateElement("Representation");
|
||||||
audio.SetAttribute("bandwidth", "200000");
|
audio.SetAttribute("bandwidth", "200000");
|
||||||
audio.SetAttribute("id", "2");
|
audio.SetAttribute("id", info.Itag);
|
||||||
audio.SetAttribute("sampleRate", streamInfo.Audio.SampleRate);
|
audio.SetAttribute("sampleRate", info.SampleRate);
|
||||||
audio.SetAttribute("numChannels", streamInfo.Audio.ChannelsCount);
|
audio.SetAttribute("numChannels", info.ChannelsCount);
|
||||||
audio.SetAttribute("codecs", list.Audio.First(i => i.Container.GetFileExtension() == "webm").AudioEncoding.ToString());
|
audio.SetAttribute("codecs", info.Codecs);
|
||||||
audio.SetAttribute("mimeType", $"audio/{list.Audio.First(i => i.Container.GetFileExtension() == "webm").Container.GetFileExtension()}");
|
audio.SetAttribute("mimeType", info.MimeType);
|
||||||
audioSet.AppendChild(audio);
|
|
||||||
|
|
||||||
XmlElement audioUrl = doc.CreateElement("BaseURL");
|
XmlElement audioUrl = doc.CreateElement("BaseURL");
|
||||||
audioUrl.InnerText = list.Audio.First(i => i.Container.GetFileExtension() == "webm").Url;
|
audioUrl.InnerText = info.Url;
|
||||||
audio.AppendChild(audioUrl);
|
audio.AppendChild(audioUrl);
|
||||||
|
|
||||||
XmlElement audioSegmentBase = doc.CreateElement("SegmentBase");
|
XmlElement audioSegmentBase = doc.CreateElement("SegmentBase");
|
||||||
audioSegmentBase.SetAttribute("indexRange", streamInfo.Audio.IndexRange);
|
audioSegmentBase.SetAttribute("indexRange", info.IndexRange);
|
||||||
audioSegmentBase.SetAttribute("indexRangeExact", "true");
|
|
||||||
audio.AppendChild(audioSegmentBase);
|
audio.AppendChild(audioSegmentBase);
|
||||||
|
|
||||||
XmlElement audioInit = doc.CreateElement("Initialization");
|
XmlElement audioInit = doc.CreateElement("Initialization");
|
||||||
audioInit.SetAttribute("range", streamInfo.Audio.InitRange);
|
audioInit.SetAttribute("range", info.InitRange);
|
||||||
audioSegmentBase.AppendChild(audioInit);
|
audioSegmentBase.AppendChild(audioInit);
|
||||||
|
|
||||||
doc.AppendChild(mpd);
|
return audio;
|
||||||
mpd.AppendChild(period);
|
|
||||||
period.AppendChild(videoSet);
|
|
||||||
period.AppendChild(audioSet);
|
|
||||||
|
|
||||||
doc.Save(await manifest.OpenStreamForWriteAsync());
|
|
||||||
|
|
||||||
//TODO: Fix this shit. It doesn't work
|
|
||||||
return $"ms-appdata:///roaming/{manifest.Name}".ToUri();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<StreamInfo> GetInfoAsync(Video info, VideoStreamInfo requestedQuality)
|
private static async Task<StreamInfo> GetInfoAsync(Video info, VideoStreamInfo requestedQuality)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
StreamInfo si = new StreamInfo();
|
||||||
HttpClient http = new HttpClient();
|
HttpClient http = new HttpClient();
|
||||||
string response = HttpUtility.HtmlDecode(await http.GetStringAsync($"https://youtube.com/embed/{info.Id}?disable_polymer=true&hl=en"));
|
|
||||||
|
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);
|
IHtmlDocument videoEmbedPageHtml = new HtmlParser().Parse(response);
|
||||||
|
|
||||||
string playerConfigRaw = Regex.Match(videoEmbedPageHtml.Source.Text,
|
string playerConfigRaw = Regex.Match(videoEmbedPageHtml.Source.Text,
|
||||||
@"yt\.setConfig\({'PLAYER_CONFIG': (?<Json>\{[^\{\}]*(((?<Open>\{)[^\{\}]*)+((?<Close-Open>\})[^\{\}]*)+)*(?(Open)(?!))\})")
|
@"ytplayer\.config = (?<Json>\{[^\{\}]*(((?<Open>\{)[^\{\}]*)+((?<Close-Open>\})[^\{\}]*)+)*(?(Open)(?!))\})")
|
||||||
.Groups["Json"].Value;
|
.Groups["Json"].Value;
|
||||||
JToken playerConfigJson = JToken.Parse(playerConfigRaw);
|
JToken playerConfigJson = JToken.Parse(playerConfigRaw);
|
||||||
string sts = playerConfigJson.SelectToken("sts").Value<string>();
|
|
||||||
|
|
||||||
string eurl = WebUtility.UrlEncode($"https://youtube.googleapis.com/v/{info.Id}");
|
var playerResponseRaw = playerConfigJson.SelectToken("args.player_response").Value<string>();
|
||||||
string url = $"https://youtube.com/get_video_info?video_id={info.Id}&el=embedded&sts={sts}&eurl={eurl}&hl=en";
|
JToken playerResponseJson = JToken.Parse(playerResponseRaw);
|
||||||
string raw = await http.GetStringAsync(url);
|
string errorReason = playerResponseJson.SelectToken("playabilityStatus.reason")?.Value<string>();
|
||||||
|
if (!string.IsNullOrWhiteSpace(errorReason))
|
||||||
|
throw new InvalidDataException($"Video [{info.Id}] is unplayable. Reason: {errorReason}");
|
||||||
|
|
||||||
Dictionary<string, string> videoInfoDic = SplitQuery(raw);
|
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"));
|
||||||
|
|
||||||
StreamInfo si = new StreamInfo();
|
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"]
|
||||||
|
});
|
||||||
|
|
||||||
List<Dictionary<string, string>> adaptiveStreamInfosUrl = videoInfoDic.GetValueOrDefault("adaptive_fmts").Split(',').Select(SplitQuery).ToList();
|
foreach (var i in audio)
|
||||||
Dictionary<string, string> video = adaptiveStreamInfosUrl.Find(i => i["quality_label"] == requestedQuality.VideoQualityLabel && i["type"].Contains(requestedQuality.Container.GetFileExtension()));
|
si.Audio.Add(new StreamInfo.AudioInfo
|
||||||
Dictionary<string, string> audio = adaptiveStreamInfosUrl.Find(i => i.ContainsKey("audio_sample_rate") && i["type"].Contains("webm"));
|
{
|
||||||
|
ChannelsCount = i["audio_channels"],
|
||||||
si.Video.IndexRange = video["index"];
|
IndexRange = i["index"],
|
||||||
si.Audio.ChannelsCount = audio["audio_channels"];
|
SampleRate = i["audio_sample_rate"],
|
||||||
si.Audio.IndexRange = audio["index"];
|
Codecs = i["type"].Split('"')[1],
|
||||||
si.Audio.SampleRate = audio["audio_sample_rate"];
|
MimeType = i["type"].Split(';')[0],
|
||||||
|
Url = i["url"],
|
||||||
|
Itag = i["itag"]
|
||||||
|
});
|
||||||
|
|
||||||
return si;
|
return si;
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
return null;
|
return new StreamInfo();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,57 +218,32 @@ namespace FoxTube.Controls.Player
|
|||||||
return dic;
|
return dic;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AppendVideoSet(XmlDocument doc, XmlElement root, List<VideoStreamInfo> list)
|
private static string GetBandwidth(string quality)
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
|
string parsed = quality.Split('p')[0];
|
||||||
switch (quality)
|
switch (quality)
|
||||||
{
|
{
|
||||||
case VideoQuality.High4320:
|
case "4320":
|
||||||
return $"16763040";
|
return $"16763040";
|
||||||
case VideoQuality.High3072:
|
case "3072":
|
||||||
return $"11920384";
|
return $"11920384";
|
||||||
case VideoQuality.High2880:
|
case "2880":
|
||||||
return $"11175360";
|
return $"11175360";
|
||||||
case VideoQuality.High2160:
|
case "2160":
|
||||||
return $"8381520";
|
return $"8381520";
|
||||||
case VideoQuality.High1440:
|
case "1440":
|
||||||
return $"5587680";
|
return $"5587680";
|
||||||
case VideoQuality.High1080:
|
case "1080":
|
||||||
return $"4190760";
|
return $"4190760";
|
||||||
case VideoQuality.High720:
|
case "720":
|
||||||
return $"2073921";
|
return $"2073921";
|
||||||
case VideoQuality.Medium480:
|
case "480":
|
||||||
return $"869460";
|
return $"869460";
|
||||||
case VideoQuality.Medium360:
|
case "360":
|
||||||
return $"686521";
|
return $"686521";
|
||||||
case VideoQuality.Low240:
|
case "240":
|
||||||
return $"264835";
|
return $"264835";
|
||||||
case VideoQuality.Low144:
|
case "144":
|
||||||
default:
|
default:
|
||||||
return $"100000";
|
return $"100000";
|
||||||
}
|
}
|
||||||
@@ -263,7 +283,7 @@ namespace FoxTube.Controls.Player
|
|||||||
|
|
||||||
list.Add(new StreamQuality
|
list.Add(new StreamQuality
|
||||||
{
|
{
|
||||||
Resolution = "Auto",
|
Resolution = ResourceLoader.GetForCurrentView("VideoPage").GetString("/VideoPage/auto"),
|
||||||
Url = url.ToUri()
|
Url = url.ToUri()
|
||||||
});
|
});
|
||||||
list.Reverse();
|
list.Reverse();
|
||||||
@@ -285,6 +305,14 @@ namespace FoxTube.Controls.Player
|
|||||||
{
|
{
|
||||||
public string IndexRange { get; set; }
|
public string IndexRange { get; set; }
|
||||||
public string InitRange => $"0-{int.Parse(IndexRange.Split('-')[0]) - 1}";
|
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 class AudioInfo
|
||||||
{
|
{
|
||||||
@@ -292,10 +320,14 @@ namespace FoxTube.Controls.Player
|
|||||||
public string InitRange => $"0-{int.Parse(IndexRange.Split('-')[0]) - 1}";
|
public string InitRange => $"0-{int.Parse(IndexRange.Split('-')[0]) - 1}";
|
||||||
public string SampleRate { get; set; }
|
public string SampleRate { get; set; }
|
||||||
public string ChannelsCount { 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 VideoInfo Video { get; } = new VideoInfo();
|
public List<VideoInfo> Video { get; } = new List<VideoInfo>();
|
||||||
public AudioInfo Audio { get; } = new AudioInfo();
|
public List<AudioInfo> Audio { get; } = new List<AudioInfo>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class StreamQuality
|
public class StreamQuality
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ namespace FoxTube
|
|||||||
object info = (quality.SelectedItem as ComboBoxItem).Tag;
|
object info = (quality.SelectedItem as ComboBoxItem).Tag;
|
||||||
if (info is MuxedStreamInfo)
|
if (info is MuxedStreamInfo)
|
||||||
Player.SetPlaybackSource(MediaSource.CreateFromUri((info as MuxedStreamInfo).Url.ToUri()));
|
Player.SetPlaybackSource(MediaSource.CreateFromUri((info as MuxedStreamInfo).Url.ToUri()));
|
||||||
else if (info is VideoStreamInfo)
|
else if (info is VideoStreamInfo || info == null)
|
||||||
Player.SetPlaybackSource(MediaSource.CreateFromUri(await ManifestGenerator.GetManifest(Meta, info as VideoStreamInfo, MediaStreams)));
|
Player.SetPlaybackSource(MediaSource.CreateFromUri(await ManifestGenerator.GetManifest(Meta, info as VideoStreamInfo, MediaStreams)));
|
||||||
else if (info is StreamQuality)
|
else if (info is StreamQuality)
|
||||||
Player.SetPlaybackSource(MediaSource.CreateFromUri((info as StreamQuality).Url));
|
Player.SetPlaybackSource(MediaSource.CreateFromUri((info as StreamQuality).Url));
|
||||||
@@ -437,6 +437,11 @@ namespace FoxTube
|
|||||||
qualityList.Sort(new QualityComparer());
|
qualityList.Sort(new QualityComparer());
|
||||||
qualityList.Reverse();
|
qualityList.Reverse();
|
||||||
|
|
||||||
|
quality.Items.Add(new ComboBoxItem
|
||||||
|
{
|
||||||
|
Content = ResourceLoader.GetForCurrentView("VideoPage").GetString("/VideoPage/auto")
|
||||||
|
});
|
||||||
|
|
||||||
foreach (string i in qualityList)
|
foreach (string i in qualityList)
|
||||||
{
|
{
|
||||||
object tag;
|
object tag;
|
||||||
|
|||||||
@@ -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"/>
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -300,4 +300,7 @@
|
|||||||
<data name="continue" xml:space="preserve">
|
<data name="continue" xml:space="preserve">
|
||||||
<value>Continue from</value>
|
<value>Continue from</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="auto" xml:space="preserve">
|
||||||
|
<value>Auto</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -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>
|
||||||
@@ -300,4 +300,7 @@
|
|||||||
<data name="continue" xml:space="preserve">
|
<data name="continue" xml:space="preserve">
|
||||||
<value>Продолжить с</value>
|
<value>Продолжить с</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="auto" xml:space="preserve">
|
||||||
|
<value>Авто</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
Reference in New Issue
Block a user