Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 22 additions & 3 deletions app/components-react/windows/go-live/PlatformSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import { InstagramEditStreamInfo } from './platforms/InstagramEditStreamInfo';
import { KickEditStreamInfo } from './platforms/KickEditStreamInfo';
import AdvancedSettingsSwitch from './AdvancedSettingsSwitch';
import { TInputLayout } from 'components-react/shared/inputs';
import { inject } from 'slap';
import { HighlighterService } from 'app-services';

export default function PlatformSettings() {
const {
Expand All @@ -33,7 +35,12 @@ export default function PlatformSettings() {
isUpdateMode,
isTikTokConnected,
layout,
isDualOutputMode,
isAiHighlighterEnabled,
enabledPlatformsCount,
} = useGoLiveSettings().extend(settings => ({
highlighterService: inject(HighlighterService),

get descriptionIsRequired() {
const fbSettings = settings.state.platforms['facebook'];
const descriptionIsRequired = fbSettings && fbSettings.enabled && !fbSettings.useCustomFields;
Expand All @@ -47,6 +54,18 @@ export default function PlatformSettings() {
get layout(): TInputLayout {
return settings.isAdvancedMode ? 'horizontal' : 'vertical';
},

get isDualOutputMode() {
return settings.isDualOutputMode;
},

get isAiHighlighterEnabled() {
return this.highlighterService.aiHighlighterFeatureEnabled;
},

get enabledPlatformsCount() {
return settings.enabledPlatforms.length;
},
}));

const shouldShowSettings = !error && !isLoading;
Expand All @@ -62,12 +81,12 @@ export default function PlatformSettings() {
return {
isUpdateMode,
layoutMode,
isDualOutputMode,
isAiHighlighterEnabled,
enabledPlatformsCount,
get value() {
return getDefined(settings.platforms[platform]);
},
get enabledPlatformsCount() {
return enabledPlatforms.length;
},
onChange(newSettings) {
updatePlatform(platform, newSettings);
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,7 @@ export interface IPlatformComponentParams<T extends TPlatform> {
layout?: TInputLayout;
isUpdateMode?: boolean;
isScheduleMode?: boolean;
isDualOutputMode?: boolean;
isAiHighlighterEnabled?: boolean;
enabledPlatformsCount?: number;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CommonPlatformFields } from '../CommonPlatformFields';
import React from 'react';
import React, { useMemo } from 'react';
import { $t } from '../../../../services/i18n';
import { TwitchTagsInput } from './TwitchTagsInput';
import GameSelector from '../GameSelector';
Expand All @@ -10,22 +10,18 @@ import { ITwitchStartStreamOptions } from '../../../../services/platforms/twitch
import InputWrapper from 'components-react/shared/inputs/InputWrapper';
import TwitchContentClassificationInput from './TwitchContentClassificationInput';
import AiHighlighterToggle from '../AiHighlighterToggle';
import { Services } from 'components-react/service-provider';
import Badge from 'components-react/shared/DismissableBadge';
import { EDismissable } from 'services/dismissables';
import styles from './TwitchEditStreamInfo.m.less';
import cx from 'classnames';

export function TwitchEditStreamInfo(p: IPlatformComponentParams<'twitch'>) {
const twSettings = p.value;
const aiHighlighterFeatureEnabled = Services.HighlighterService.aiHighlighterFeatureEnabled;

function updateSettings(patch: Partial<ITwitchStartStreamOptions>) {
p.onChange({ ...twSettings, ...patch });
}

const enhancedBroadcastingTooltipText = $t(
'Enhanced broadcasting automatically optimizes your settings to encode and send multiple video qualities to Twitch. Selecting this option will send basic information about your computer and software setup.',
);
const bind = createBinding(twSettings, updatedSettings => updateSettings(updatedSettings));

const optionalFields = (
Expand All @@ -43,23 +39,24 @@ export function TwitchEditStreamInfo(p: IPlatformComponentParams<'twitch'>) {
layout={p.layout}
className={cx(styles.twitchCheckbox, { [styles.hideLabel]: p.layout === 'vertical' })}
>
<div>
<CheckboxInput
style={{ display: 'inline-block' }}
label={$t('Enhanced broadcasting')}
tooltip={enhancedBroadcastingTooltipText}
{...bind.isEnhancedBroadcasting}
/>
<Badge
style={{ display: 'inline-block' }}
dismissableKey={EDismissable.EnhancedBroadcasting}
content={'Beta'}
/>
</div>
<CheckboxInput
style={{ display: 'inline-block' }}
label={$t('Enhanced broadcasting')}
tooltip={$t(
'Enhanced broadcasting automatically optimizes your settings to encode and send multiple video qualities to Twitch. Selecting this option will send basic information about your computer and software setup.',
)}
{...bind.isEnhancedBroadcasting}
/>
<Badge
style={{ display: 'inline-block' }}
dismissableKey={EDismissable.EnhancedBroadcasting}
content={'Beta'}
/>
</InputWrapper>
)}
</div>
);

return (
<Form name="twitch-settings">
<PlatformSettingsLayout
Expand All @@ -77,7 +74,7 @@ export function TwitchEditStreamInfo(p: IPlatformComponentParams<'twitch'>) {
requiredFields={
<React.Fragment key="required-fields">
<GameSelector key="required" platform={'twitch'} {...bind.game} layout={p.layout} />
{aiHighlighterFeatureEnabled && (
{p.isAiHighlighterEnabled && (
<AiHighlighterToggle key="ai-toggle" game={bind.game?.value} cardIsExpanded={false} />
)}
</React.Fragment>
Expand Down
3 changes: 2 additions & 1 deletion app/i18n/en-US/streaming.json
Original file line number Diff line number Diff line change
Expand Up @@ -320,5 +320,6 @@
"Cannot change displays while live.": "Cannot change displays while live.",
"Stream switcher cannot be used in dual output mode.": "Stream switcher cannot be used in dual output mode.",
"Cannot toggle dual output in Studio Mode.": "Cannot toggle dual output in Studio Mode.",
"Dual Output will be disabled since not supported in this mode.": "Dual Output will be disabled since not supported in this mode."
"Dual Output will be disabled since not supported in this mode.": "Dual Output will be disabled since not supported in this mode.",
"Stream Error : %{targetName}": "Stream Error : %{targetName}"
}
1 change: 1 addition & 0 deletions app/services/api/external-api/streaming/streaming.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ interface IStreamingState {
verticalRecordingStatusTime?: string;
replayBufferStatus: EReplayBufferState;
replayBufferStatusTime: string;
enhancedBroadcasting?: boolean;
streamErrorCreated?: string;
}

Expand Down
22 changes: 15 additions & 7 deletions app/services/diagnostics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@ import * as remote from '@electron/remote';
import { AppService } from 'services/app';
import fs from 'fs';
import path from 'path';
import { platformList, TPlatform } from './platforms';
import { getPlatformService, platformList, TPlatform } from './platforms';
import { TDisplayType } from './settings-v2';
import { getWmiClass } from 'util/wmi';
import { ITwitchStartStreamOptions } from './platforms/twitch';

interface IStreamDiagnosticInfo {
startTime: number;
Expand Down Expand Up @@ -503,15 +504,19 @@ export class DiagnosticsService extends PersistentStatefulService<IDiagnosticsSe
} as Partial<IStreamDiagnosticInfo>;

if (!this.dualOutputService.views.dualOutputMode && platforms.replace(/"/g, '') === 'twitch') {
const enhancedBroadcasting = this.outputSettingsService.getIsEnhancedBroadcasting();
const enhancedBroadcasting = this.streamingService.getModel().enhancedBroadcasting;
const twService = getPlatformService('twitch');
const settingsMatch =
enhancedBroadcasting &&
(twService.state.settings as ITwitchStartStreamOptions).isEnhancedBroadcasting;

if (enhancedBroadcasting.setting === 'Enabled' && enhancedBroadcasting.live !== 'Enabled') {
if (!settingsMatch) {
this.logProblem(
'Twitch Enhanced Broadcasting setting enabled but did not go live with Enhanced Broadcasting.',
);
}

info.enhancedBroadcasting = enhancedBroadcasting.live;
info.enhancedBroadcasting = enhancedBroadcasting ? 'Enabled' : 'Disabled';
}

if (this.dualOutputService.views.dualOutputMode) {
Expand Down Expand Up @@ -1096,7 +1101,7 @@ export class DiagnosticsService extends PersistentStatefulService<IDiagnosticsSe
);
}

return {
const info = {
'Start Time': new Date(s.startTime).toString(),
'End Time': s.endTime ? new Date(s.endTime).toString() : 'Stream did not end cleanly',
'Skipped Frames': `${s.pctSkipped?.toFixed(2)}%`,
Expand All @@ -1108,9 +1113,12 @@ export class DiagnosticsService extends PersistentStatefulService<IDiagnosticsSe
Platforms: platforms,
Destinations: s?.destinations,
'Stream Type': s?.type,
'Enhanced Broadcasting':
s?.enhancedBroadcasting !== undefined ? s.enhancedBroadcasting : 'N/A',
};

if (s?.enhancedBroadcasting !== undefined) {
(info as any)['Enhanced Broadcasting'] = s.enhancedBroadcasting;
}
return info;
}),
);
}
Expand Down
19 changes: 18 additions & 1 deletion app/services/platforms/twitch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { SettingsService } from 'services/settings';
import { TTwitchOAuthScope, TwitchTagsService } from './twitch/index';
import { platformAuthorizedRequest } from './utils';
import { CustomizationService } from 'services/customization';
import { IGoLiveSettings } from 'services/streaming';
import { IGoLiveSettings, TDisplayOutput } from 'services/streaming';
import { InheritMutations, mutation } from 'services/core';
import { StreamError, throwStreamError, TStreamErrorType } from 'services/streaming/stream-error';
import { BasePlatformService } from './base-platform';
Expand All @@ -35,6 +35,7 @@ export interface ITwitchStartStreamOptions {
game?: string;
video?: IVideo;
tags: string[];
display?: TDisplayOutput;
mode?: TOutputOrientation;
contentClassificationLabels: string[];
isBrandedContent: boolean;
Expand Down Expand Up @@ -191,6 +192,16 @@ export class TwitchService
}

async beforeGoLive(goLiveSettings?: IGoLiveSettings, context?: TDisplayType) {
// Enhanced broadcasting is currently only available with single output single stream
// To prevent errors, correctly update it here when streaming in other modes.
if (
(goLiveSettings && goLiveSettings.streamShift) ||
this.streamingService.views.isMultiplatformMode ||
this.streamingService.views.isDualOutputMode
) {
this.settingsService.actions.setEnhancedBroadcasting(false);
}

// If the stream has switched from another device, a new broadcast does not need to be created
if (
goLiveSettings &&
Expand Down Expand Up @@ -232,6 +243,12 @@ export class TwitchService
this.setPlatformContext('twitch');
}

async afterStopStream() {
if (this.state.settings.isEnhancedBroadcasting) {
this.settingsService.actions.setEnhancedBroadcasting(true);
}
}

async validatePlatform() {
const twitchTwoFactorCheck = this.fetchStreamKey()
.then(key => {
Expand Down
28 changes: 0 additions & 28 deletions app/services/settings/output/output-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -939,32 +939,4 @@ export class OutputSettingsService extends Service {
return EObsSimpleEncoder.x264_lowcpu;
}
}

/**
* Fetch enhanced broadcasting setting from the backend
* @remark This function is used in the diagnostics report to determine if a stream
* went live with enhanced broadcasting enabled. It should not be used for logic.
* This only represents the setting in the backend but not the setting in the Twitch service,
* which is the actual source of truth.
* @returns string representation of the setting for the diagnositics report
*/
getIsEnhancedBroadcasting() {
try {
const enhancedBroadcasting = this.settingsService.isEnhancedBroadcasting();
const twService = getPlatformService('twitch');
return {
setting: (twService.state.settings as ITwitchStartStreamOptions).isEnhancedBroadcasting
? 'Enabled'
: 'Disabled',
live: enhancedBroadcasting ? 'Enabled' : 'Disabled',
};
} catch (e: unknown) {
console.error('Error getting enhanced broadcasting setting:', e);

return {
setting: 'Unknown',
live: 'Unknown',
};
}
}
}
9 changes: 9 additions & 0 deletions app/services/streaming/stream-error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,7 @@ export function throwStreamError(
export function formatStreamErrorMessage(
errorTypeOrError?: TStreamErrorType | StreamError,
target?: string,
statusText?: string,
): IErrorMessages {
const messages = {
user: [] as string[],
Expand All @@ -343,6 +344,14 @@ export function formatStreamErrorMessage(
messages.user.push(details);
}

if (target) {
messages.user.push($t('Stream Error : %{target}', { target }));
}

if (statusText && !message.split(' ').includes('blocked')) {
messages.user.push(statusText);
}

const reportMessage = (error as any)?.action ? `${message}, ${(error as any).action}` : message;

messages.report.push(reportMessage.replace(/[.,]$/, ''));
Expand Down
1 change: 1 addition & 0 deletions app/services/streaming/streaming-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ export interface IStreamingServiceState {
replayBufferStatusTime: string;
selectiveRecording: boolean;
dualOutputMode: boolean;
enhancedBroadcasting?: boolean;
info: IStreamInfo;
}

Expand Down
22 changes: 17 additions & 5 deletions app/services/streaming/streaming.ts
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,11 @@ export class StreamingService
? undefined
: settings;

if (platform === 'twitch' && settingsForPlatform && !this.views.isDualOutputMode) {
const isEnhancedBroadcasting = this.settingsService.isEnhancedBroadcasting();
this.SET_ENHANCED_BROADCASTING(isEnhancedBroadcasting);
}

// don't update settings for twitch in unattendedMode
await this.runCheck(platform, () => service.beforeGoLive(settingsForPlatform, display));
} catch (e: unknown) {
Expand Down Expand Up @@ -862,10 +867,6 @@ export class StreamingService
* Set the error state for the GoLive window
*/
private setError(errorTypeOrError?: TStreamErrorType | StreamError, platform?: TPlatform) {
const target = platform
? this.views.getPlatformDisplayName(platform)
: $t('Custom Destination');

const streamError =
errorTypeOrError instanceof StreamError
? errorTypeOrError
Expand All @@ -875,7 +876,11 @@ export class StreamingService
streamError.platform = platform;
}

const messages = formatStreamErrorMessage(streamError, target);
const target = streamError.platform
? this.views.getPlatformDisplayName(streamError.platform)
: $t('Custom Destination');

const messages = formatStreamErrorMessage(streamError, target, streamError.statusText);
this.streamErrorUserMessage = messages.user;
this.streamErrorReportMessage = messages.report;

Expand Down Expand Up @@ -1178,6 +1183,8 @@ export class StreamingService
this.restreamService.resetStreamShift();
}

this.SET_ENHANCED_BROADCASTING(false);

this.UPDATE_STREAM_INFO({ lifecycle: 'empty' });
return Promise.resolve();
}
Expand Down Expand Up @@ -1801,6 +1808,11 @@ export class StreamingService
this.state.dualOutputMode = enabled;
}

@mutation()
private SET_ENHANCED_BROADCASTING(enabled: boolean) {
this.state.enhancedBroadcasting = enabled;
}

@mutation()
private SET_WARNING(warningType: 'YT_AUTO_START_IS_DISABLED') {
this.state.info.warning = warningType;
Expand Down
Loading