Skip to content

Commit c09f447

Browse files
authored
Streaming error alert and bypass. (#5308)
* Streaming error alert and bypass. * Fix error UI. * Fix Kick external. * Add default error message.
1 parent d18e455 commit c09f447

File tree

9 files changed

+112
-77
lines changed

9 files changed

+112
-77
lines changed

app/components-react/pages/PlatformMerge.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export default function PlatformMerge(p: IPlatformMergeProps) {
3838

3939
async function mergePlatform() {
4040
if (!platform) return;
41-
const mode = ['youtube', 'twitch', 'twitter', 'tiktok'].includes(platform)
41+
const mode = ['youtube', 'twitch', 'twitter', 'tiktok', 'kick'].includes(platform)
4242
? 'external'
4343
: 'internal';
4444
await UserService.actions.return

app/components-react/windows/go-live/GoLiveError.m.less

+3-9
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,15 @@
2020
font-family: monospace;
2121
text-align: right;
2222
align-self: center !important;
23+
margin-top: 10px;
2324
}
2425

2526
.go-prime-btn {
2627
background-color: var(--prime-hover);
2728
}
2829

2930
.cta-btn {
30-
display: grid;
31+
display: flex;
3132
flex-direction: row;
32-
justify-content: space-between;
33-
34-
p {
35-
justify-self: flex-end;
36-
align-self: flex-end;
37-
margin: 0px;
38-
position: absolute;
39-
}
33+
justify-content: flex-end;
4034
}

app/components-react/windows/go-live/GoLiveError.tsx

+40-3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { $t } from '../../../services/i18n';
99
import Translate from '../../shared/Translate';
1010
import css from './GoLiveError.m.less';
1111
import * as remote from '@electron/remote';
12+
import { ENotificationType } from 'services/notifications';
1213

1314
/**
1415
* Shows an error and troubleshooting suggestions
@@ -198,11 +199,47 @@ export default function GoLiveError() {
198199
}
199200

200201
function renderRestreamError(error: IStreamError) {
202+
Services.NotificationsService.actions.push({
203+
message: `${$t('Multistream Error')}: ${error.details}`,
204+
type: ENotificationType.WARNING,
205+
lifeTime: 5000,
206+
});
207+
208+
function skipSettingsUpdateAndGoLive() {
209+
StreamingService.actions.finishStartStreaming();
210+
WindowsService.actions.closeChildWindow();
211+
}
212+
213+
const details =
214+
!error.details || error.details === ''
215+
? [
216+
$t(
217+
'One of destinations might have incomplete permissions. Reconnect the destinations in settings and try again.',
218+
),
219+
]
220+
: error.details.split('\n');
221+
201222
return (
202-
<MessageLayout error={error}>
203-
{$t(
204-
'Please try again. If the issue persists, you can stream directly to a single platform instead.',
223+
<MessageLayout
224+
error={error}
225+
hasButton={true}
226+
message={$t(
227+
'Please try again. If the issue persists, you can stream directly to a single platform instead or click the button below to bypass and go live.',
205228
)}
229+
>
230+
{`${$t('Issues')}:`}
231+
<ul>
232+
{details.map((detail: string, index: number) => (
233+
<li key={`detail-${index}`}>{detail}</li>
234+
))}
235+
</ul>
236+
<button
237+
className="button button--warn"
238+
style={{ marginTop: '8px' }}
239+
onClick={() => skipSettingsUpdateAndGoLive()}
240+
>
241+
{$t('Bypass and Go Live')}
242+
</button>
206243
</MessageLayout>
207244
);
208245
}

app/components-react/windows/go-live/MessageLayout.tsx

+5-7
Original file line numberDiff line numberDiff line change
@@ -62,19 +62,17 @@ export default function MessageLayout(p: IMessageLayoutProps & HTMLAttributes<un
6262
<div>{p.children}</div>
6363
<div className={cx({ [styles.ctaBtn]: hasButton })}>
6464
{details && !isErrorDetailsShown && (
65-
<p style={{ textAlign: 'right' }}>
66-
<a className={styles.link} onClick={() => setDetailsShown(true)}>
67-
{$t('Show details')}
68-
</a>
69-
</p>
65+
<a className={styles.link} onClick={() => setDetailsShown(true)}>
66+
{$t('Show details')}
67+
</a>
7068
)}
7169
{details && isErrorDetailsShown && (
72-
<p className={styles.details}>
70+
<div className={styles.details}>
7371
{details}
7472
<br />
7573
<br />
7674
{error?.status} {error?.statusText} {error?.url}
77-
</p>
75+
</div>
7876
)}
7977
</div>
8078
</div>

app/i18n/ar-SA/settings.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -261,5 +261,6 @@
261261
"Idle": "Idle",
262262
"Please connect platforms directly from Streamlabs Desktop instead of adding Streamlabs Multistream as a custom destination": "Please connect platforms directly from Streamlabs Desktop instead of adding Streamlabs Multistream as a custom destination",
263263
"Audio Encoder": "Audio Encoder",
264-
"Additional Settings": "Additional Settings"
264+
"Additional Settings": "Additional Settings",
265+
"Recording Audio Encoder": "Recording Audio Encoder"
265266
}

app/i18n/en-US/streaming.json

+6-1
Original file line numberDiff line numberDiff line change
@@ -280,5 +280,10 @@
280280
"Copy stream link": "Copy stream link",
281281
"Copy %{platform} link": "Copy %{platform} link",
282282
"Copied to clipboard": "Copied to clipboard",
283-
"Twitch Followers": "Twitch Followers"
283+
"Twitch Followers": "Twitch Followers",
284+
"Bypass and Go Live": "Bypass and Go Live",
285+
"Please try again. If the issue persists, you can stream directly to a single platform instead or click the button below to bypass and go live.": "Please try again. If the issue persists, you can stream directly to a single platform instead or click the button below to bypass and go live.",
286+
"Issues": "Issues",
287+
"Multistream Error": "Multistream Error",
288+
"One of destinations might have incomplete permissions. Reconnect the destinations in settings and try again.": "One of destinations might have incomplete permissions. Reconnect the destinations in settings and try again."
284289
}

app/services/platforms/kick.ts

+43-48
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,6 @@ import { getDefined } from 'util/properties-type-guards';
1919
import { WindowsService } from 'services/windows';
2020
import { DiagnosticsService } from 'services/diagnostics';
2121

22-
interface IKickGame {
23-
id: number;
24-
name: string;
25-
thumbnail: string;
26-
}
27-
2822
interface IKickStartStreamResponse {
2923
id?: string;
3024
rtmp: string;
@@ -141,33 +135,26 @@ export class KickService
141135
const kickSettings = getDefined(goLiveSettings.platforms.kick);
142136
const context = display ?? kickSettings?.display;
143137

144-
try {
145-
const streamInfo = await this.startStream(
146-
goLiveSettings.platforms.kick ?? this.state.settings,
138+
const streamInfo = await this.startStream(goLiveSettings.platforms.kick ?? this.state.settings);
139+
140+
this.SET_INGEST(streamInfo.rtmp);
141+
this.SET_STREAM_KEY(streamInfo.key);
142+
this.SET_CHAT_URL(streamInfo.chat_url);
143+
this.SET_PLATFORM_ID(streamInfo.platform_id);
144+
145+
if (!this.streamingService.views.isMultiplatformMode) {
146+
this.streamSettingsService.setSettings(
147+
{
148+
streamType: 'rtmp_custom',
149+
key: streamInfo.key,
150+
server: streamInfo.rtmp,
151+
},
152+
context,
147153
);
148-
149-
this.SET_INGEST(streamInfo.rtmp);
150-
this.SET_STREAM_KEY(streamInfo.key);
151-
this.SET_CHAT_URL(streamInfo.chat_url);
152-
this.SET_PLATFORM_ID(streamInfo.platform_id);
153-
154-
if (!this.streamingService.views.isMultiplatformMode) {
155-
this.streamSettingsService.setSettings(
156-
{
157-
streamType: 'rtmp_custom',
158-
key: streamInfo.key,
159-
server: streamInfo.rtmp,
160-
},
161-
context,
162-
);
163-
}
164-
165-
this.SET_STREAM_SETTINGS(kickSettings);
166-
this.setPlatformContext('kick');
167-
} catch (e: unknown) {
168-
console.error('Error starting stream: ', e);
169-
throwStreamError('PLATFORM_REQUEST_FAILED', e as any);
170154
}
155+
156+
this.SET_STREAM_SETTINGS(kickSettings);
157+
this.setPlatformContext('kick');
171158
}
172159

173160
async afterStopStream(): Promise<void> {
@@ -253,8 +240,21 @@ export class KickService
253240
if (!e) throwStreamError('PLATFORM_REQUEST_FAILED', defaultError);
254241

255242
// check if the error is an IKickError
256-
if (typeof e === 'object' && e.hasOwnProperty('success')) {
243+
if (typeof e === 'object' && e.hasOwnProperty('result')) {
257244
const error = e as IKickError;
245+
246+
if (error.result && error.result.data.code === 401) {
247+
const message = error.statusText !== '' ? error.statusText : error.result.data.message;
248+
throwStreamError(
249+
'KICK_SCOPE_OUTDATED',
250+
{
251+
status: error.status,
252+
statusText: message,
253+
},
254+
error.result.data.message,
255+
);
256+
}
257+
258258
throwStreamError(
259259
'PLATFORM_REQUEST_FAILED',
260260
{
@@ -266,7 +266,7 @@ export class KickService
266266
);
267267
}
268268

269-
throwStreamError('PLATFORM_REQUEST_FAILED', e as any, defaultError.statusText);
269+
throwStreamError('PLATFORM_REQUEST_FAILED', e);
270270
});
271271
}
272272

@@ -291,20 +291,16 @@ export class KickService
291291
.catch(e => {
292292
console.warn('Error fetching Kick info: ', e);
293293

294-
if (e.hasOwnProperty('result')) {
295-
if (e.result.data.code === 401) {
296-
const message = e.statusText !== '' ? e.statusText : e.result.data.message;
297-
throwStreamError(
298-
'KICK_SCOPE_OUTDATED',
299-
{
300-
status: e.status,
301-
statusText: message,
302-
},
303-
e.result.data.message,
304-
);
305-
}
306-
307-
throwStreamError('PLATFORM_REQUEST_FAILED', e);
294+
if (e.result && e.result.data.code === 401) {
295+
const message = e.statusText !== '' ? e.statusText : e.result.data.message;
296+
throwStreamError(
297+
'KICK_SCOPE_OUTDATED',
298+
{
299+
status: e.status,
300+
statusText: message,
301+
},
302+
e.result.data.message,
303+
);
308304
}
309305
});
310306
}
@@ -324,7 +320,6 @@ export class KickService
324320
return jfetch<IKickStreamInfoResponse>(request)
325321
.then(async res => {
326322
const data = res as IKickStreamInfoResponse;
327-
console.log('data', JSON.stringify(data, null, 2));
328323

329324
if (data.categories && data.categories.length > 0) {
330325
const games = await Promise.all(

app/services/streaming/stream-error.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ export function formatStreamErrorMessage(
311311
messages.user.push(details);
312312
}
313313

314-
message = error.message.replace(/\s*\.$/, '');
314+
message = error.message.replace(/\.*$/, '');
315315
// trim trailing periods so that the message joins correctly
316316
const errorMessage = (error as any)?.action ? `${message}, ${(error as any).action}` : message;
317317

app/services/streaming/streaming.ts

+11-6
Original file line numberDiff line numberDiff line change
@@ -701,17 +701,23 @@ export class StreamingService
701701
}
702702

703703
handleTypedStreamError(
704-
e: unknown,
704+
e: StreamError | unknown,
705705
type: TStreamErrorType,
706706
message: string,
707707
): StreamError | TStreamErrorType {
708-
console.error(message, e as any);
709-
710708
// restream errors returns an object with key value pairs for error details
711-
if (e instanceof StreamError && type.split('_').includes('RESTREAM')) {
709+
const defaultMessage =
710+
this.state.info.error?.message ??
711+
$t(
712+
'One of destinations might have incomplete permissions. Reconnect the destinations in settings and try again.',
713+
);
714+
715+
if (e && typeof e === 'object' && type.split('_').includes('RESTREAM')) {
712716
const messages: string[] = [];
713717
const details: string[] = [];
714718

719+
details.push(defaultMessage);
720+
715721
Object.entries(e).forEach(([key, value]: [string, string]) => {
716722
const name = capitalize(key.replace(/([A-Z])/g, ' $1'));
717723
// only show the error message for the stream key and server url to the user for security purposes
@@ -722,8 +728,7 @@ export class StreamingService
722728
}
723729
});
724730

725-
e.message = messages.join('. ');
726-
e.details = details.join('.');
731+
return createStreamError(type, { status: 400, statusText: message }, details.join('\n'));
727732
}
728733

729734
return e instanceof StreamError ? { ...e, type } : type;

0 commit comments

Comments
 (0)