В этом руководстве объясняется, как использовать SDK YouTube Playables с веб-приложением Flutter.
Конфигурация флаттера
Веб-приложение Flutter по умолчанию требует некоторых изменений, чтобы работать в качестве Playable. Следующие изменения необходимы начиная с версии Flutter 3.24.5.
По умолчанию Flutter настроен на загрузку ресурсов из корневого каталога, тогда как Playables требует, чтобы ресурсы загружались относительно точки входа. Это будет отображаться либо как ошибка 404, либо, возможно, как ошибка, которая отображается в консоли javascript и гласит: Refused to execute script from... because its MIME type ('text/html') is not executable.
Один из способов перенастроить это — удалить следующий тег из файла index.html:
<base href="$FLUTTER_BASE_HREF">
По умолчанию Flutter загружает некоторые библиотеки динамически, а не встраивает их в приложение. CanvasKit является примером этого. Это будет отображаться как ошибка в консоли javascript, которая гласит: Refused to connect to '...' because it violates the following Content Security Policy directive: "connect-src 'self' blob: data:".
Flutter также может выводить дополнительные журналы при сбое загрузки, например Failed to download any of the following CanvasKit URLs
. Чтобы это исправить, вы можете создать свое веб-приложение со следующим дополнительным флагом:
$ flutter build web --no-web-resources-cdn
По умолчанию веб-приложения Flutter загружают шрифты динамически, а не встраивают их в приложение. Приложение Flutter по умолчанию использует шрифт Roboto, и это будет отображаться как ошибка в консоли javascript, которая гласит: Refused to connect to '...' because it violates the following Content Security Policy directive: "connect-src 'self' blob: data".
Чтобы исправить это, вы можете предпринять следующие шаги:
- Создайте папку с именем «шрифты» в корне вашего веб-приложения Flutter.
- Загрузите семейство шрифтов Roboto с сайта fonts.google.com.
- Извлеките шрифты и скопируйте Roboto-Regular.ttf в папку шрифтов.
- Добавьте следующую строку в pubspec.yaml вашего веб-приложения Flutter:
fonts:
- family: Roboto
fonts:
- asset: fonts/Roboto-Regular.ttf
Более подробную информацию о настройке шрифтов можно найти в документации Flutter .
Интеграция SDK
SDK YouTube Playables можно использовать из веб-игры Flutter через оболочку JS-interop, подобную этой:
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;
Использование
- Следуйте инструкциям по настройке и инициализации веб-SDK .
- Измените файл
web/index.html
вашего веб-приложения Flutter, включив в него необходимые теги сценария.
- Измените файл
- Добавьте копию
ytgame.dart
вsrc
вашего веб-приложения Flutter. - Убедитесь, что пакет js добавлен во Flutter, например, выполнив команду
flutter pub add js
. - Добавьте
import 'src/location/of/ytgame.dart';
где это необходимо. - Используйте объект
ytgame
для доступа к SDK.
Примеры
Ниже вы найдете пару примеров использования Dart API.
firstFrameReady
и gameReady
Чтобы игра запускалась правильно, вам нужно будет вызвать firstFrameReady
, когда первый кадр будет готов к отображению, и gameReady
, когда игра будет готова к взаимодействию. Один из способов сделать это в приложении Flutter по умолчанию — добавить вызовы в файл main.dart:
...
import 'src/ytgame.dart';
...
void main() {
ytgame.game.firstFrameReady();
ytgame.game.gameReady();
runApp(const MyApp());
}
Обратите внимание, что это всего лишь пример запуска вашей игры — вам нужно будет правильно разместить расположение этих вызовов, чтобы обеспечить правильную реализацию.
sendScore
В этом примере показана реализация sendScore(int points)
в 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
Это пример того, как игра может прослушивать события Pause
, поступающие от YT Playables, чтобы при необходимости приостановить работу своего движка:
...
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();
}
См. полный справочник по API YT Playables .