Streamlink - Almost done.

This commit is contained in:
Heavy Bob 2025-04-10 01:55:35 +10:00
parent f7fb63c50f
commit d7057efe15
6 changed files with 385 additions and 264 deletions

View file

@ -19,6 +19,7 @@ namespace AutoTrackR2
"AutoTrackR2", "AutoTrackR2",
"crash.log" "crash.log"
); );
private StreamlinkHandler? _streamlinkHandler;
protected override void OnStartup(StartupEventArgs e) protected override void OnStartup(StartupEventArgs e)
{ {
@ -43,6 +44,7 @@ namespace AutoTrackR2
} }
base.OnStartup(e); base.OnStartup(e);
_streamlinkHandler = new StreamlinkHandler();
} }
private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)

View file

@ -2,14 +2,13 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="410" Height="410"
Width="626"> Width="700">
<Grid Background="{DynamicResource BackgroundLightBrush}"> <Grid Background="{DynamicResource BackgroundLightBrush}">
<!-- Main Layout Grid --> <!-- Main Layout Grid -->
<Grid Margin="0,0,5,7"> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<!-- One row for the content, the other for buttons -->
<RowDefinition Height="*"/> <RowDefinition Height="*"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
@ -22,9 +21,9 @@
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<!-- Section for Config Fields --> <!-- Section for Config Fields -->
<StackPanel Grid.Column="0" <StackPanel Grid.Row="0"
VerticalAlignment="Center" VerticalAlignment="Top"
Height="389"> Margin="20,10,20,0">
<!-- Log File --> <!-- Log File -->
<StackPanel Margin="0,5,0,10" <StackPanel Margin="0,5,0,10"
Orientation="Horizontal"> Orientation="Horizontal">
@ -38,11 +37,11 @@
FontSize="16" FontSize="16"
Margin="0,5,0,5" Margin="0,5,0,5"
FontFamily="{StaticResource Roboto}"/> FontFamily="{StaticResource Roboto}"/>
<StackPanel Orientation="Horizontal" <StackPanel Orientation="Horizontal">
Margin="30,0,0,0">
<TextBox Name="LogFilePath" <TextBox Name="LogFilePath"
Width="330" Width="260"
Height="30" Height="30"
Margin="10,0,0,0"
Style="{StaticResource RoundedTextBox}"/> Style="{StaticResource RoundedTextBox}"/>
<Button Content="Browse" <Button Content="Browse"
Width="75" Width="75"
@ -51,6 +50,13 @@
Margin="5,0" Margin="5,0"
Style="{StaticResource ButtonStyle}" Style="{StaticResource ButtonStyle}"
Click="LogFileBrowseButton_Click"/> Click="LogFileBrowseButton_Click"/>
<Button Content="Open"
Width="75"
Height="30"
FontFamily="{StaticResource Orbitron}"
Margin="5,0"
Style="{StaticResource ButtonStyle}"
Click="LogFileOpenButton_Click"/>
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
@ -66,11 +72,11 @@
Foreground="{DynamicResource TextBrush}" Foreground="{DynamicResource TextBrush}"
FontSize="16" FontSize="16"
Margin="0,5,0,5"/> Margin="0,5,0,5"/>
<StackPanel Orientation="Horizontal" <StackPanel Orientation="Horizontal">
Margin="30,0,0,0">
<TextBox Name="ApiUrl" <TextBox Name="ApiUrl"
Width="330" Width="260"
Height="30" Height="30"
Margin="10,0,0,0"
Style="{StaticResource RoundedTextBox}"/> Style="{StaticResource RoundedTextBox}"/>
<Button Content="Test" <Button Content="Test"
Width="75" Width="75"
@ -95,9 +101,9 @@
FontSize="16" FontSize="16"
Margin="0,5,0,5"/> Margin="0,5,0,5"/>
<PasswordBox Name="ApiKey" <PasswordBox Name="ApiKey"
Width="330" Width="260"
Height="30" Height="30"
Margin="33,0,0,0" Margin="10,0,0,0"
Style="{StaticResource RoundedPasswordBox}"/> Style="{StaticResource RoundedPasswordBox}"/>
</StackPanel> </StackPanel>
@ -115,7 +121,7 @@
Margin="0,5,0,5"/> Margin="0,5,0,5"/>
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<TextBox Name="VideoPath" <TextBox Name="VideoPath"
Width="330" Width="260"
Height="30" Height="30"
Margin="10,0,0,0" Margin="10,0,0,0"
Style="{StaticResource RoundedTextBox}"/> Style="{StaticResource RoundedTextBox}"/>
@ -126,6 +132,13 @@
Margin="5,0" Margin="5,0"
Style="{StaticResource ButtonStyle}" Style="{StaticResource ButtonStyle}"
Click="VideoPathBrowseButton_Click"/> Click="VideoPathBrowseButton_Click"/>
<Button Content="Open"
Width="75"
Height="30"
FontFamily="{StaticResource Orbitron}"
Margin="5,0"
Style="{StaticResource ButtonStyle}"
Click="VideoPathOpenButton_Click"/>
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
@ -146,7 +159,8 @@
<!-- Left Column Controls --> <!-- Left Column Controls -->
<!-- Visor Wipe Toggle --> <!-- Visor Wipe Toggle -->
<StackPanel Grid.Column="0" Grid.Row="0" <StackPanel Grid.Column="0"
Grid.Row="0"
Orientation="Horizontal" Orientation="Horizontal"
Margin="0,0,5,5"> Margin="0,0,5,5">
<TextBlock Text="ⓘ" <TextBlock Text="ⓘ"
@ -171,7 +185,8 @@
</StackPanel> </StackPanel>
<!-- Video Record Toggle --> <!-- Video Record Toggle -->
<StackPanel Grid.Column="0" Grid.Row="1" <StackPanel Grid.Column="0"
Grid.Row="1"
Orientation="Horizontal" Orientation="Horizontal"
Margin="0,0,5,5"> Margin="0,0,5,5">
<TextBlock Text="ⓘ" <TextBlock Text="ⓘ"
@ -196,7 +211,8 @@
</StackPanel> </StackPanel>
<!-- Offline Mode Toggle --> <!-- Offline Mode Toggle -->
<StackPanel Grid.Column="0" Grid.Row="2" <StackPanel Grid.Column="0"
Grid.Row="2"
Orientation="Horizontal" Orientation="Horizontal"
Margin="0,0,5,5"> Margin="0,0,5,5">
<TextBlock Text="ⓘ" <TextBlock Text="ⓘ"
@ -223,7 +239,8 @@
<!-- Right Column Controls --> <!-- Right Column Controls -->
<!-- Streamlink Toggle --> <!-- Streamlink Toggle -->
<StackPanel Grid.Column="1" Grid.Row="0" <StackPanel Grid.Column="1"
Grid.Row="0"
Orientation="Horizontal" Orientation="Horizontal"
Margin="5,0,0,5"> Margin="5,0,0,5">
<TextBlock Text="ⓘ" <TextBlock Text="ⓘ"
@ -248,7 +265,8 @@
</StackPanel> </StackPanel>
<!-- Streamlink Duration --> <!-- Streamlink Duration -->
<StackPanel Grid.Column="1" Grid.Row="1" <StackPanel Grid.Column="1"
Grid.Row="1"
Orientation="Horizontal" Orientation="Horizontal"
Margin="5,0,0,5"> Margin="5,0,0,5">
<TextBlock Text="ⓘ" <TextBlock Text="ⓘ"
@ -262,8 +280,8 @@
Margin="0,7,0,0"/> Margin="0,7,0,0"/>
<Slider Name="StreamlinkDurationSlider" <Slider Name="StreamlinkDurationSlider"
Minimum="10" Minimum="10"
Maximum="300" Maximum="600"
TickFrequency="10" TickFrequency="30"
IsSnapToTickEnabled="True" IsSnapToTickEnabled="True"
Value="30" Value="30"
Style="{StaticResource SliderStyle}" Style="{StaticResource SliderStyle}"
@ -278,7 +296,8 @@
</StackPanel> </StackPanel>
<!-- Test Streamlink Button --> <!-- Test Streamlink Button -->
<Button Grid.Column="1" Grid.Row="2" <Button Grid.Column="1"
Grid.Row="2"
Content="Test Streamlink" Content="Test Streamlink"
Width="120" Width="120"
Height="30" Height="30"
@ -288,10 +307,11 @@
Click="TestStreamlinkButton_Click"/> Click="TestStreamlinkButton_Click"/>
<!-- Theme Slider --> <!-- Theme Slider -->
<StackPanel Grid.Column="0" Grid.Row="4" <StackPanel Grid.Column="0"
Grid.Row="3"
Grid.ColumnSpan="2" Grid.ColumnSpan="2"
Orientation="Horizontal" Orientation="Horizontal"
Margin="0,10,0,5"> Margin="0,0,0,5">
<TextBlock Text="Theme:" <TextBlock Text="Theme:"
Foreground="{DynamicResource TextBrush}" Foreground="{DynamicResource TextBrush}"
FontSize="16" FontSize="16"
@ -302,25 +322,24 @@
TickFrequency="1" TickFrequency="1"
IsSnapToTickEnabled="True" IsSnapToTickEnabled="True"
ValueChanged="ThemeSlider_ValueChanged" ValueChanged="ThemeSlider_ValueChanged"
Width="447" Width="350"
Style="{StaticResource ThreePositionSlider}"/> Style="{StaticResource ThreePositionSlider}"/>
</StackPanel> </StackPanel>
</Grid> </Grid>
</StackPanel> </StackPanel>
<!-- Save Button --> <!-- Save Button -->
<StackPanel Grid.Column="2"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Margin="0,0,0,10">
<Button x:Name="SaveButton" <Button x:Name="SaveButton"
Grid.Row="1"
Content="Save" Content="Save"
Width="100" Width="100"
Height="40" Height="40"
Style="{StaticResource ButtonStyle}" Style="{StaticResource ButtonStyle}"
FontFamily="{StaticResource Orbitron}" FontFamily="{StaticResource Orbitron}"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Margin="0,0,80,20"
Click="SaveButton_Click"/> Click="SaveButton_Click"/>
</StackPanel>
</Grid> </Grid>
</Grid> </Grid>
</UserControl> </UserControl>

View file

@ -37,9 +37,18 @@ public partial class ConfigPage : UserControl
VideoRecordSlider.Value = ConfigManager.VideoRecord; VideoRecordSlider.Value = ConfigManager.VideoRecord;
OfflineModeSlider.Value = ConfigManager.OfflineMode; OfflineModeSlider.Value = ConfigManager.OfflineMode;
ThemeSlider.Value = ConfigManager.Theme; ThemeSlider.Value = ConfigManager.Theme;
StreamlinkSlider.Value = ConfigManager.StreamlinkEnabled;
StreamlinkDurationSlider.Value = ConfigManager.StreamlinkDuration;
// Initialize Streamlink slider style // Initialize Streamlink slider style
if (StreamlinkSlider.Value == 0)
{
StreamlinkSlider.Style = (Style)Application.Current.FindResource("FalseToggleStyle"); StreamlinkSlider.Style = (Style)Application.Current.FindResource("FalseToggleStyle");
}
else
{
StreamlinkSlider.Style = (Style)Application.Current.FindResource("ToggleSliderStyle");
}
ApplyToggleModeStyle(OfflineModeSlider.Value, VisorWipeSlider.Value, VideoRecordSlider.Value); ApplyToggleModeStyle(OfflineModeSlider.Value, VisorWipeSlider.Value, VideoRecordSlider.Value);
@ -430,6 +439,19 @@ public partial class ConfigPage : UserControl
private void StreamlinkSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) private void StreamlinkSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{ {
Slider slider = (Slider)sender; Slider slider = (Slider)sender;
// Only allow enabling if streamlink is installed
if (slider.Value == 1)
{
if (!StreamlinkHandler.IsStreamlinkInstalled())
{
MessageBox.Show("Streamlink is not installed. Please install Streamlink to use this feature.",
"Error", MessageBoxButton.OK, MessageBoxImage.Error);
slider.Value = 0;
return;
}
}
ConfigManager.StreamlinkEnabled = (int)slider.Value; ConfigManager.StreamlinkEnabled = (int)slider.Value;
// Apply the appropriate style based on the value // Apply the appropriate style based on the value
@ -460,81 +482,45 @@ public partial class ConfigPage : UserControl
// Get the duration from the slider // Get the duration from the slider
int duration = (int)StreamlinkDurationSlider.Value; int duration = (int)StreamlinkDurationSlider.Value;
// Check if streamlink is installed // Calculate the total duration (30 seconds before + configured duration)
var versionInfo = new ProcessStartInfo int totalDuration = 30 + duration;
{
FileName = "streamlink",
Arguments = "--version",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
};
using (var process = Process.Start(versionInfo)) // Test recording functionality, we're using spacecutlet cause need a good tester.
{ TrackREventDispatcher.OnStreamlinkRecordEvent("spacecutlet");
if (process == null)
{
MessageBox.Show("Streamlink is not installed. Please install Streamlink to use this feature.",
"Error", MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
string output = process.StandardOutput.ReadToEnd();
process.WaitForExit();
if (process.ExitCode != 0)
{
MessageBox.Show("Streamlink is not installed or not working correctly. Please install Streamlink to use this feature.",
"Error", MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
}
// Test recording functionality
var testUrl = "https://www.twitch.tv/starcitizen"; // Example URL for testing
var outputPath = Path.Combine(
ConfigManager.VideoPath ?? Environment.GetFolderPath(Environment.SpecialFolder.MyVideos),
"streamlink_test.mp4"
);
var recordInfo = new ProcessStartInfo
{
FileName = "streamlink",
Arguments = $"{testUrl} best -o {outputPath}",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
};
using (var process = Process.Start(recordInfo))
{
if (process == null)
{
MessageBox.Show("Failed to start test recording.", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
// Wait for a short duration (5 seconds) to test recording
await Task.Delay(5000);
try
{
if (!process.HasExited)
{
process.Kill();
}
}
catch { }
}
MessageBox.Show($"Streamlink test recording completed successfully.\nA 5-second test recording was saved to:\n{outputPath}",
"Success", MessageBoxButton.OK, MessageBoxImage.Information);
} }
catch (Exception ex) catch (Exception ex)
{ {
MessageBox.Show($"Error testing Streamlink: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show($"Error testing Streamlink: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
} }
} }
private void LogFileOpenButton_Click(object sender, RoutedEventArgs e)
{
if (!string.IsNullOrEmpty(LogFilePath.Text))
{
string directory = Path.GetDirectoryName(LogFilePath.Text) ?? string.Empty;
if (Directory.Exists(directory))
{
Process.Start("explorer.exe", directory);
}
else
{
MessageBox.Show("Directory does not exist.", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}
private void VideoPathOpenButton_Click(object sender, RoutedEventArgs e)
{
if (!string.IsNullOrEmpty(VideoPath.Text) && Directory.Exists(VideoPath.Text))
{
Process.Start("explorer.exe", VideoPath.Text);
}
else
{
MessageBox.Show("Directory does not exist.", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
} }
public class Theme public class Theme

View file

@ -0,0 +1,88 @@
using System.Diagnostics;
using System.IO;
namespace AutoTrackR2;
public class StreamlinkHandler
{
public StreamlinkHandler()
{
TrackREventDispatcher.StreamlinkRecordEvent += HandleStreamlinkRecord;
}
public static bool IsStreamlinkInstalled()
{
try
{
var checkInfo = new ProcessStartInfo
{
FileName = "where",
Arguments = "streamlink",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
};
using (var process = Process.Start(checkInfo))
{
if (process == null)
{
return false;
}
string output = process.StandardOutput.ReadToEnd();
process.WaitForExit();
return process.ExitCode == 0 && !string.IsNullOrEmpty(output);
}
}
catch
{
return false;
}
}
private async void HandleStreamlinkRecord(string streamerHandle)
{
if (ConfigManager.StreamlinkEnabled != 1)
{
return;
}
try
{
var outputPath = Path.Combine(
ConfigManager.VideoPath ?? Environment.GetFolderPath(Environment.SpecialFolder.MyVideos),
$"kill_{DateTime.Now:yyyyMMdd_HHmmss}.mp4"
);
// Calculate the duration for recording (30 seconds before + configured duration after)
var totalDuration = 30 + ConfigManager.StreamlinkDuration;
var recordInfo = new ProcessStartInfo
{
FileName = "streamlink",
Arguments = $"https://www.twitch.tv/{streamerHandle} best --hls-live-edge 30 --hls-duration {totalDuration} -o {outputPath}",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
};
using (var process = Process.Start(recordInfo))
{
if (process == null)
{
return;
}
// Wait for the recording to complete
await process.WaitForExitAsync();
}
}
catch (Exception ex)
{
// Log the error but don't crash the application
Debug.WriteLine($"Error recording streamlink clip: {ex.Message}");
}
}
}

View file

@ -54,4 +54,11 @@ public static class TrackREventDispatcher
{ {
JumpDriveStateChangedEvent?.Invoke(data); JumpDriveStateChangedEvent?.Invoke(data);
} }
public static event Action<string>? StreamlinkRecordEvent;
public static void OnStreamlinkRecordEvent(string streamerHandle)
{
StreamlinkRecordEvent?.Invoke(streamerHandle);
}
} }

View file

@ -172,7 +172,26 @@ public static class WebHandler
else if (response.StatusCode == HttpStatusCode.OK) else if (response.StatusCode == HttpStatusCode.OK)
{ {
Console.WriteLine("Successfully submitted kill data"); Console.WriteLine("Successfully submitted kill data");
Console.WriteLine($"Response: {await response.Content.ReadAsStringAsync()}"); var responseContent = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Response: {responseContent}");
// Check if the response contains a streamer field
try
{
var responseData = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(responseContent);
if (responseData != null && responseData.TryGetValue("streamer", out JsonElement streamerElement))
{
string streamerHandle = streamerElement.GetString() ?? string.Empty;
if (!string.IsNullOrEmpty(streamerHandle))
{
TrackREventDispatcher.OnStreamlinkRecordEvent(streamerHandle);
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Error parsing streamer from response: {ex.Message}");
}
} }
} }
} }