In diesem Leitfaden wird beschrieben, wie du das YouTube Playables SDK mit einer Flutter-Web-App verwendest.
Flutter-Konfiguration
Die Standard-Flutter-Web-App muss für die Verwendung als Playable etwas angepasst werden. Die folgenden Änderungen sind ab Flutter-Version 3.24.5 erforderlich.
Standardmäßig ist Flutter so konfiguriert, dass Ressourcen aus dem Stammverzeichnis geladen werden. Bei Playables müssen Ressourcen jedoch relativ zum Einstiegspunkt geladen werden. Dies wird entweder als 404-Fehler oder möglicherweise als Fehler in der JavaScript-Konsole angezeigt: Refused to execute script
from... because its MIME type ('text/html') is not executable.
Eine Möglichkeit zur Neukonfiguration besteht darin, das folgende Tag aus der Datei „index.html“ zu entfernen:
<base href="$FLUTTER_BASE_HREF">
Standardmäßig lädt Flutter einige Bibliotheken dynamisch, anstatt sie in die App einzubetten. CanvasKit ist ein Beispiel dafür. Dies wird in der JavaScript-Konsole als Fehler angezeigt: Refused to connect to '...' because it
violates the following Content Security Policy directive: "connect-src 'self'
blob: data:".
Flutter kann auch zusätzliche Protokolle ausgeben, wenn das Laden fehlschlägt, z. B. Failed to download any of the following CanvasKit URLs
. Sie können das Problem beheben, indem Sie Ihre Webanwendung mit dem folgenden zusätzlichen Flag erstellen:
$ flutter build web --no-web-resources-cdn
Standardmäßig laden Flutter-Webanwendungen Schriftarten dynamisch, anstatt sie in die App einzubetten. Die Standard-Flutter-App verwendet die Schriftart „Roboto“, was in der JavaScript-Konsole als Fehler angezeigt wird: Refused to connect to '...'
because it violates the following Content Security Policy directive:
"connect-src 'self' blob: data".
So beheben Sie das Problem:
- Erstellen Sie im Stammverzeichnis Ihrer Flutter-Webanwendung einen Ordner mit dem Namen „fonts“.
- Laden Sie die Schriftfamilie „Roboto“ von fonts.google.com herunter.
- Extrahieren Sie die Schriftarten und kopieren Sie „Roboto-Regular.ttf“ in den Schriftordner.
- Fügen Sie der pubspec.yaml Ihrer Flutter-Web-App die folgende Zeile hinzu:
fonts:
- family: Roboto
fonts:
- asset: fonts/Roboto-Regular.ttf
Weitere Informationen zur Schriftkonfiguration finden Sie in der Flutter-Dokumentation.
SDK-Integration
Das YouTube Playables SDK kann in einem Flutter-Webspiel über einen JS-Interop-Wrapper wie diesen verwendet werden:
ytgame.dart
// Copyright 2023 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@JS()
library ytgame_api;
import 'package:js/js.dart';
import 'package:js/js_util.dart' as js_util;
enum PlayablesErrorType {
unknown('UNKNOWN'),
apiUnavailable('API_UNAVAILABLE'),
invalidParams('INVALID_PARAMS'),
sizeLimitExceeded('SIZE_LIMIT_EXCEEDED');
const PlayablesErrorType(String type) : _type = type;
final String _type;
@override
String toString() => _type;
static PlayablesErrorType fromString(String errorType) {
return values.firstWhere(
(element) => element._type == errorType,
);
}
}
@JS()
@staticInterop
class PlayablesError {}
extension PlayablesErrorExtension on PlayablesError {
@JS('errorType')
external String get _errorType;
PlayablesErrorType get errorType {
return PlayablesErrorType.fromString(_errorType);
}
}
@JS()
@anonymous
@staticInterop
class PlayablesScore {
external factory PlayablesScore({
required num value,
});
}
extension PlayablesScoreExtension on PlayablesScore {
external num get value;
}
// engagement
@JS()
@staticInterop
class PlayablesEngagement {}
extension PlayablesEngagementExtension on PlayablesEngagement {
@JS('sendScore')
external Object _sendScore(PlayablesScore score);
Future<void> sendScore(PlayablesScore score) {
return js_util.promiseToFuture(_sendScore(score));
}
}
// game
@JS()
@staticInterop
class PlayablesGame {}
extension PlayablesGameExtension on PlayablesGame {
external void firstFrameReady();
external void gameReady();
@JS('loadData')
external Object _loadData();
Future<String?> loadData() {
return js_util.promiseToFuture<String?>(_loadData());
}
@JS('saveData')
external Object _saveData(String? data);
Future<void> saveData(String? data) {
return js_util.promiseToFuture<void>(_saveData(data));
}
}
// health
@JS()
@staticInterop
class PlayablesHealth {}
extension PlayablesHealthExtension on PlayablesHealth {
external void logError();
external void logWarning();
}
// system
typedef PlayablesUnsetCallback = void Function();
typedef PlayablesOnAudioEnabledChange = void Function(bool isAudioEnabled);
typedef PlayablesOnPause = void Function();
typedef PlayablesOnResume = void Function();
@JS()
@staticInterop
class PlayablesSystem {}
extension PlayablesSystemExtension on PlayablesSystem {
external bool isAudioEnabled();
@JS('onAudioEnabledChange')
external PlayablesUnsetCallback _onAudioEnabledChange(
PlayablesOnAudioEnabledChange callback);
PlayablesUnsetCallback onAudioEnabledChange(
PlayablesOnAudioEnabledChange callback) {
return _onAudioEnabledChange(allowInterop(callback));
}
@JS('onPause')
external PlayablesUnsetCallback _onPause(PlayablesOnPause callback);
PlayablesUnsetCallback onPause(PlayablesOnPause callback) {
return _onPause(allowInterop(callback));
}
@JS('onResume')
external PlayablesUnsetCallback _onResume(PlayablesOnResume callback);
PlayablesUnsetCallback onResume(PlayablesOnResume callback) {
return _onResume(allowInterop(callback));
}
}
@JS()
@staticInterop
class Playables {}
extension PlayablesExtension on Playables {
@JS('SDK_VERSION')
external String get sdkVersion;
external PlayablesEngagement get engagement;
external PlayablesGame get game;
external PlayablesHealth get health;
external PlayablesSystem get system;
}
@JS('ytgame')
external Playables get ytgame;
Nutzung
- Folgen Sie der Anleitung zum Einrichten und Initialisieren des Web SDK.
- Ändern Sie die
web/index.html
Ihrer Flutter-Web-App so, dass sie die erforderlichen Script-Tags enthält.
- Ändern Sie die
- Fügen Sie eine Kopie von
ytgame.dart
in densrc
Ihrer Flutter-Webanwendung ein. - Achten Sie darauf, dass das JS-Paket zu Flutter hinzugefügt wurde. Sie können dazu beispielsweise den Befehl
flutter pub add js
ausführen. - Fügen Sie bei Bedarf
import 'src/location/of/ytgame.dart';
hinzu. - Verwende das
ytgame
-Objekt, um auf das SDK zuzugreifen.
Beispiele
Unten finden Sie einige Beispiele für die Dart API in Aktion.
firstFrameReady
und gameReady
Damit Ihr Spiel richtig gestartet wird, müssen Sie firstFrameReady
aufrufen, wenn der erste Frame angezeigt werden kann, und gameReady
, wenn mit dem Spiel interagiert werden kann. In der Standard-Flutter-App können Sie die Aufrufe beispielsweise der Datei „main.dart“ hinzufügen:
...
import 'src/ytgame.dart';
...
void main() {
ytgame.game.firstFrameReady();
ytgame.game.gameReady();
runApp(const MyApp());
}
Hinweis: Dies ist nur ein Beispiel, um Ihr Spiel zum Laufen zu bringen. Sie müssen die Aufrufe an der richtigen Stelle platzieren, um eine korrekte Implementierung zu ermöglichen.
sendScore
Dieses Beispiel zeigt eine Implementierung von sendScore(int points)
in Dart:
...
import 'src/ytgame.dart';
...
/// Sends [points] as score to YT Playables
Future<void> sendScore(int points) async {
// Create a score object...
final PlayablesScore score = PlayablesScore(
value: points,
);
// Submit the score to ytgame
await ytgame.engagement.sendScore(score);
}
onPause
Hier siehst du ein Beispiel dafür, wie ein Spiel Pause
-Ereignisse von YT-Playables verarbeiten kann, um bei Bedarf die Engine zu pausieren:
...
import 'src/ytgame.dart';
...
/// The instance of your game.
late Game myGame;
/// Callback to stop listening for Pause events (cleanup).
PlayablesUnsetCallback? _unsetOnPause;
...
/// Sets a [callback] to respond to Pause events.
void registerOnPauseListener(PlayablesOnPause callback) {
_unsetOnPause = ytgame.system.onPause(callback);
}
/// Stops listening to Pause events, and clears [_unsetOnPause].
void unsetOnPauseListener() {
if (_unsetOnPause != null) {
_unsetOnPause!();
_unsetOnPause = null;
}
}
...
/// Initialization for your game
void initGame() {
...
myGame = ...
registerOnPauseListener(_onPause);
}
void _onPause() {
// This is called when a Pause event is received from YT Playables.
myGame.pauseEngine();
}
/// Teardown for your game
void disposeGame() {
...
unsetOnPauseListener();
}