Skip to content

Commit

Permalink
Add streamlink support #23
Browse files Browse the repository at this point in the history
  • Loading branch information
laurencee committed Jan 26, 2017
1 parent 757e8e7 commit 396fadc
Show file tree
Hide file tree
Showing 12 changed files with 71 additions and 40 deletions.
10 changes: 10 additions & 0 deletions Livestream.Monitor/Core/Settings.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using Caliburn.Micro;
using Livestream.Monitor.Core.UI;
using Livestream.Monitor.Model;
Expand Down Expand Up @@ -170,8 +171,17 @@ public string TwitchAuthToken
}
}

/// <summary>
/// Flag to indicate if the twitch oauth token has been defined either in livestream monitor settings
/// or in the livestreamer/streamlink configuration file
/// </summary>
public bool TwitchAuthTokenSet => TwitchAuthTokenInLivestreamerConfig ||
!string.IsNullOrWhiteSpace(TwitchAuthToken);

/// <summary>
/// Name of the livestreamer/streamlink exe without the file extension
/// </summary>
public string LivestreamExeDisplayName => Path.GetFileNameWithoutExtension(LivestreamerFullPath);
}

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion Livestream.Monitor/Model/ApiClients/TwitchApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public string LivestreamerAuthorizationArg
public async Task Authorize(IViewAware screen)
{
var messageDialogResult = await screen.ShowMessageAsync(title: "Authorization",
message: "Twitch now requires authorization to connect to their services, have you already set your oauth token in your livestreamer configuration file?",
message: $"Twitch requires authorization to connect to their services, have you set your oauth token in your {settingsHandler.Settings.LivestreamExeDisplayName} configuration file?",
messageDialogStyle: MessageDialogStyle.AffirmativeAndNegative,
dialogSettings: new MetroDialogSettings()
{
Expand Down
2 changes: 1 addition & 1 deletion Livestream.Monitor/Model/ApiClients/YoutubeApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ private async Task<List<LivestreamQueryResult>> QueryChannels(
LivestreamModel = new LivestreamModel("offline", channelIdentifier)
{
DisplayName = channelIdentifier.ChannelId,
Description = "[Livestream Monitor - offline youtube stream]",
Description = "[Offline youtube stream]",
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Linq;
using System.Threading.Tasks;
using Caliburn.Micro;
using Livestream.Monitor.Core;
using Livestream.Monitor.Model.ApiClients;

namespace Livestream.Monitor.Model.Monitoring
Expand Down Expand Up @@ -122,6 +123,11 @@ public Task RemoveLivestream(ChannelIdentifier channelIdentifier)
return Task.CompletedTask;
}

public void SetDefaultSelectedStreamQuality()
{
SelectedStreamQuality = StreamQuality.Best.ToString();
}

protected virtual void OnOnlineLivestreamsRefreshComplete()
{
LivestreamsRefreshComplete?.Invoke(this, EventArgs.Empty);
Expand Down
2 changes: 2 additions & 0 deletions Livestream.Monitor/Model/Monitoring/IMonitorStreamsModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,7 @@ public interface IMonitorStreamsModel : INotifyPropertyChangedEx

/// <summary> Refreshing data for all followed livestreams </summary>
Task RefreshLivestreams();

void SetDefaultSelectedStreamQuality();
}
}
43 changes: 21 additions & 22 deletions Livestream.Monitor/Model/Monitoring/MonitorStreamsModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,6 @@ public LivestreamModel SelectedLivestream
selectedLivestream = value;
NotifyOfPropertyChange();
NotifyOfPropertyChange(() => CanOpenStream);

InitSelectedStreamQuality();
}
}

Expand All @@ -99,8 +97,11 @@ public string SelectedStreamQuality
selectedStreamQuality = value;
NotifyOfPropertyChange();

// we have to do a string contains check because tryparse succeeds on string integer comparisons e.g. "1"
if (selectedStreamQuality != null && Enum.GetNames(typeof(StreamQuality)).Contains(selectedStreamQuality))
// update the saved default stream quality if necessary
if (!string.IsNullOrWhiteSpace(selectedStreamQuality) &&
selectedStreamQuality != settingsHandler.Settings.DefaultStreamQuality.ToString() &&
// we have to do a string contains check because tryparse succeeds on string integer comparisons e.g. "1"
Enum.GetNames(typeof(StreamQuality)).Contains(selectedStreamQuality))
{
StreamQuality streamQuality;
if (StreamQuality.TryParse(selectedStreamQuality, true, out streamQuality))
Expand Down Expand Up @@ -210,7 +211,7 @@ public async Task RefreshLivestreams()
// before the first refresh (or after some network error) all the channels are offline
// but we would already have a channel selected
// if the selected channel is now online we have to try and update the selected stream quality/allow the user to open the stream
InitSelectedStreamQuality();
SetDefaultSelectedStreamQuality();
NotifyOfPropertyChange(() => CanOpenStream);
}
}
Expand All @@ -235,6 +236,21 @@ public async Task RemoveLivestream(ChannelIdentifier channelIdentifier)
SaveLivestreams();
}

public void SetDefaultSelectedStreamQuality()
{
if (!string.IsNullOrWhiteSpace(SelectedStreamQuality) || !CanOpenStream) return;

// update the field value to prevent saving the new quality value to disk
if (selectedLivestream.IsPartner) // twitch partner specific
{
SelectedStreamQuality = settingsHandler.Settings.DefaultStreamQuality.ToString();
}
else
{
SelectedStreamQuality = StreamQuality.Best.ToString();
}
}

protected virtual void OnOnlineLivestreamsRefreshComplete()
{
LivestreamsRefreshComplete?.Invoke(this, EventArgs.Empty);
Expand Down Expand Up @@ -336,22 +352,5 @@ private void LivestreamModelOnPropertyChanged(object sender, PropertyChangedEven
settingsHandler.Settings.ExcludeFromNotifying.Remove(excludeNotify);
}
}

private void InitSelectedStreamQuality()
{
if (!string.IsNullOrWhiteSpace(selectedStreamQuality) || !CanOpenStream) return;

// update the field value to prevent saving the new quality value to disk
if (selectedLivestream.IsPartner) // twitch partner specific
{
selectedStreamQuality = settingsHandler.Settings.DefaultStreamQuality.ToString();
}
else
{
selectedStreamQuality = StreamQuality.Best.ToString();
}

NotifyOfPropertyChange(() => SelectedStreamQuality);
}
}
}
25 changes: 17 additions & 8 deletions Livestream.Monitor/Model/StreamLauncher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,11 @@ public async Task OpenStream(LivestreamModel livestreamModel, string streamQuali
}
}

var launcher = Path.GetFileName(settingsHandler.Settings.LivestreamerFullPath);

var messageBoxViewModel = ShowLivestreamerLoadMessageBox(
title: $"Stream '{livestreamModel.DisplayName}'",
messageText: $"Launching livestreamer....{Environment.NewLine}'livestreamer.exe {livestreamerArgs}'");
messageText: $"Launching {settingsHandler.Settings.LivestreamExeDisplayName}....{Environment.NewLine}'{launcher} {livestreamerArgs}'");

// Notify the user if the quality has been swapped back to source due to the livestream not being partenered (twitch specific).
if (!livestreamModel.IsPartner && streamQuality != StreamQuality.Best.ToString())
Expand Down Expand Up @@ -187,9 +189,11 @@ public async Task OpenVod(VodDetails vodDetails, IViewAware viewAware)
const int maxTitleLength = 70;
var title = vodDetails.Title?.Length > maxTitleLength ? vodDetails.Title.Substring(0, maxTitleLength) + "..." : vodDetails.Title;

var launcher = Path.GetFileName(settingsHandler.Settings.LivestreamerFullPath);

var messageBoxViewModel = ShowLivestreamerLoadMessageBox(
title: title,
messageText: $"Launching livestreamer....{Environment.NewLine}'livestreamer.exe {livestreamerArgs}'");
messageText: $"Launching {settingsHandler.Settings.LivestreamExeDisplayName}....{Environment.NewLine}'{launcher} {livestreamerArgs}'");

StartLivestreamer(livestreamerArgs, "best", messageBoxViewModel);
}
Expand Down Expand Up @@ -263,17 +267,20 @@ private void StartLivestreamer(string livestreamerArgs, string streamQuality, Me
if (preventClose)
{
messageBoxViewModel.MessageText += Environment.NewLine + Environment.NewLine +
"ERROR occured in Livestreamer: Manually close this window when you've finished reading the livestreamer output.";
$"ERROR occured in {settingsHandler.Settings.LivestreamExeDisplayName}: " +
$"Manually close this window when you've finished reading the {settingsHandler.Settings.LivestreamExeDisplayName} output.";

// open the message box if it was somehow closed prior to the error being displayed
if (!messageBoxViewModel.IsActive) windowManager.ShowWindow(messageBoxViewModel, null, new WindowSettingsBuilder().SizeToContent().NoResizeBorderless().Create());
if (!messageBoxViewModel.IsActive)
windowManager.ShowWindow(messageBoxViewModel, null, new WindowSettingsBuilder().SizeToContent().NoResizeBorderless().Create());
}
else if (string.IsNullOrEmpty(streamQuality))
{
messageBoxViewModel.MessageText += Environment.NewLine + Environment.NewLine +
"No stream quality provided: Manually close this window when you've finished reading the livestreamer output.";
$"No stream quality provided: Manually close this window when you've finished reading the {settingsHandler.Settings.LivestreamExeDisplayName} output.";

if (!messageBoxViewModel.IsActive) windowManager.ShowWindow(messageBoxViewModel, null, new WindowSettingsBuilder().SizeToContent().NoResizeBorderless().Create());
if (!messageBoxViewModel.IsActive)
windowManager.ShowWindow(messageBoxViewModel, null, new WindowSettingsBuilder().SizeToContent().NoResizeBorderless().Create());
}
else
messageBoxViewModel.TryClose();
Expand Down Expand Up @@ -303,9 +310,11 @@ private bool CheckLivestreamerExists()

var msgBox = new MessageBoxViewModel
{
DisplayName = "Livestreamer not found",
DisplayName = "Livestreamer/Streamlink not found",
MessageText =
$"Could not find livestreamer @ {settingsHandler.Settings.LivestreamerFullPath}.{Environment.NewLine} Please download and install livestreamer from 'http://docs.livestreamer.io/install.html#windows-binaries'"
$"Could not find livestreamer/streamlink @ '{settingsHandler.Settings.LivestreamerFullPath}'" + Environment.NewLine +
"Please download and install streamlink from 'https://streamlink.github.io/install.html#windows-binaries'" + Environment.NewLine +
"OR download and install livestreamer from 'http://docs.livestreamer.io/install.html#windows-binaries'"
};

var settings = new WindowSettingsBuilder().SizeToContent()
Expand Down
2 changes: 2 additions & 0 deletions Livestream.Monitor/ViewModels/HeaderViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ private void MonitorStreamsModelOnPropertyChanged(object sender, PropertyChanged
{
var selectedLivestream = MonitorStreamsModel.SelectedLivestream;
CanOpenChat = selectedLivestream != null && selectedLivestream.ApiClient.HasChatSupport;
// would like a nicer way of resetting the stream qualities when changing selected streams or the currently selected stream changes between offline/online
StreamQualities.Clear();
if (MonitorStreamsModel.CanOpenStream)
{
Expand All @@ -250,6 +251,7 @@ private void MonitorStreamsModelOnPropertyChanged(object sender, PropertyChanged
{
StreamQualities.AddRange(new[] { StreamQuality.Best.ToString(), StreamQuality.Worst.ToString(), });
}
MonitorStreamsModel.SetDefaultSelectedStreamQuality();
}
}
}
Expand Down
5 changes: 4 additions & 1 deletion Livestream.Monitor/ViewModels/LivestreamListViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,10 @@ public async Task RefreshLivestreams()
}
}

/// <summary> Loads the selected stream through livestreamer and displays a messagebox with the loading process details </summary>
/// <summary>
/// Loads the selected stream through livestreamer/streamlink
/// and displays a messagebox with the loading process details
/// </summary>
public async Task OpenStream()
{
if (Loading) return;
Expand Down
6 changes: 3 additions & 3 deletions Livestream.Monitor/ViewModels/SettingsViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public SettingsViewModel()
if (!Execute.InDesignMode)
throw new InvalidOperationException("Constructor only accessible from design time");

LivestreamerFullPath = "Livestreamer path - design time";
LivestreamerFullPath = "Livestreamer/Streamlink path - design time";
ChromeFullPath = "Chrome path - design time";
MinimumEventViewers = 30000;
}
Expand Down Expand Up @@ -52,7 +52,7 @@ public string LivestreamerFullPath
if (value == livestreamerFullPath) return;

if (string.IsNullOrWhiteSpace(value))
AddError(nameof(LivestreamerFullPath), "Livestreamer path must not be empty");
AddError(nameof(LivestreamerFullPath), "Livestreamer/Streamlink path must not be empty");
else if (!File.Exists(value))
AddError(nameof(LivestreamerFullPath), "File not found");
else
Expand Down Expand Up @@ -189,7 +189,7 @@ public void SetLivestreamerFilePath()
if (string.IsNullOrWhiteSpace(startingPath))
startingPath = Settings.DEFAULT_LIVESTREAMER_FULL_PATH;

var livestreamerFilePath = SelectFile("Livestreamer|livestreamer.exe", startingPath);
var livestreamerFilePath = SelectFile("Livestreamer|livestreamer.exe|Streamlink|streamlink.exe", startingPath);
if (!string.IsNullOrWhiteSpace(livestreamerFilePath))
{
LivestreamerFullPath = livestreamerFilePath;
Expand Down
6 changes: 3 additions & 3 deletions Livestream.Monitor/Views/SettingsView.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>

<TextBlock Grid.Row="0" Grid.Column="0" Text="Livestreamer Path" />
<TextBlock Grid.Row="0" Grid.Column="0" Text="Livestreamer/Streamlink Path" />
<TextBox Grid.Row="1" Grid.Column="0" x:Name="LivestreamerFullPath" />
<Button Grid.Row="1" Grid.Column="1" x:Name="SetLivestreamerFilePath" Content="..." Padding="5" Margin="5,0" Height="10" />

Expand All @@ -61,8 +61,8 @@
ToolTip="When unchecked, will disable all notifications (including online notifications) from displaying" />
<CheckBox x:Name="HideStreamOutputOnLoad" Content="Hide Stream Output On Load"
ToolTip="When checked, the stream output box will be hidden upon successful stream load" />
<CheckBox x:Name="PassthroughClientId" Content="Bypass Livestreamer OAuth to twitch"
ToolTip="When checked, a client id for Livestream Monitor will provided to Livestreamer so an OAuth token for Livestreamer is not required to launch streams.&#x0a;This client id will appear in the stream output message box livestreamer args." />
<CheckBox x:Name="PassthroughClientId" Content="Bypass OAuth to twitch"
ToolTip="When checked, a client id for Livestream Monitor will provided to twitch so an OAuth token is not required to launch streams." />
</StackPanel>

<Button Grid.Row="1" x:Name="Save" Content="Save" Margin="20" />
Expand Down
2 changes: 1 addition & 1 deletion Livestream.Monitor/Views/VodListView.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
<TextBox Grid.Column="1" x:Name="VodUrl"
ToolTip="Any valid twitch vod URL can be placed in this textbox, not just urls from vod tiles." />
<Button Grid.Column="2" x:Name="OpenVod" Content="Open VOD" Margin="3"
ToolTip="Opens up the VOD through livestreamer (with HLS passthrough to allow seeking)"/>
ToolTip="Opens up the VOD through livestreamer/streamlink (with HLS passthrough to allow seeking)"/>
</Grid>


Expand Down

0 comments on commit 396fadc

Please sign in to comment.