Flutter Web SDK YouTube Ruang Game Eksperimental

Panduan ini menjelaskan cara menggunakan YouTube Playables SDK dengan aplikasi Web Flutter.

Konfigurasi Flutter

Aplikasi web Flutter default memerlukan beberapa perubahan agar dapat berfungsi sebagai Playable. Perubahan berikut diperlukan mulai Flutter versi 3.24.5.

Secara default, Flutter dikonfigurasi untuk memuat resource dari direktori root, sedangkan Playables mengharuskan resource dimuat secara relatif terhadap titik entri. Hal ini akan muncul sebagai error 404, atau mungkin sebagai error yang muncul di konsol JavaScript yang bertuliskan: Refused to execute script from... because its MIME type ('text/html') is not executable. Salah satu cara untuk mengonfigurasi ulang ini adalah dengan menghapus tag berikut dari file index.html:

<base href="$FLUTTER_BASE_HREF">

Secara default, Flutter memuat beberapa library secara dinamis, bukan menyematkannya dalam aplikasi. CanvasKit adalah contohnya. Hal ini akan muncul sebagai error di konsol JavaScript yang bertuliskan: Refused to connect to '...' because it violates the following Content Security Policy directive: "connect-src 'self' blob: data:". Flutter juga dapat menghasilkan log tambahan saat pemuatan gagal, seperti Failed to download any of the following CanvasKit URLs. Untuk memperbaikinya, Anda dapat mem-build aplikasi web dengan flag tambahan berikut:

$ flutter build web --no-web-resources-cdn

Secara default, aplikasi web Flutter memuat font secara dinamis, bukan menyematkannya di aplikasi. Aplikasi flutter default menggunakan font Roboto, dan ini akan muncul sebagai error di konsol JavaScript yang bertuliskan: Refused to connect to '...' because it violates the following Content Security Policy directive: "connect-src 'self' blob: data". Untuk memperbaikinya, Anda dapat melakukan langkah-langkah berikut:

  • Buat folder bernama "fonts" di root aplikasi web flutter Anda
  • Download jenis font Roboto dari fonts.google.com.
  • Ekstrak font, lalu salin Roboto-Regular.ttf ke folder font
  • Tambahkan baris berikut ke pubspec.yaml aplikasi web flutter Anda:
  fonts:
    - family: Roboto
      fonts:
       - asset: fonts/Roboto-Regular.ttf

Informasi selengkapnya tentang konfigurasi font dapat ditemukan di dokumentasi Flutter.

Integrasi SDK

YouTube Playables SDK dapat digunakan dari game Web Flutter melalui wrapper JS-interop yang mirip dengan ini:

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;

Penggunaan

  1. Ikuti petunjuk untuk menyiapkan dan melakukan inisialisasi SDK web.
    • Ubah web/index.html aplikasi Flutter Web Anda untuk menyertakan tag skrip yang diperlukan.
  2. Tambahkan salinan ytgame.dart di src aplikasi web Flutter Anda.
  3. Pastikan paket js ditambahkan ke Flutter, seperti dengan menjalankan perintah flutter pub add js.
  4. Tambahkan import 'src/location/of/ytgame.dart'; jika diperlukan.
  5. Gunakan objek ytgame untuk mengakses SDK.

Contoh

Temukan beberapa contoh cara kerja Dart API di bawah.

firstFrameReady dan gameReady

Agar game dimulai dengan benar, Anda harus memanggil firstFrameReady saat frame pertama siap ditampilkan, dan gameReady saat game siap berinteraksi. Salah satu cara untuk melakukannya di aplikasi Flutter default adalah dengan menambahkan panggilan ke file main.dart:

...
import 'src/ytgame.dart';
...

void main() {
  ytgame.game.firstFrameReady();
  ytgame.game.gameReady();
  runApp(const MyApp());
}

Perhatikan bahwa ini hanyalah contoh untuk menjalankan game - Anda harus menempatkan lokasi panggilan ini dengan benar untuk memberikan implementasi yang tepat.

sendScore

Contoh ini menunjukkan implementasi sendScore(int points) di 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

Berikut adalah contoh cara game dapat memproses peristiwa Pause yang berasal dari YT Playables, untuk menjeda mesinnya jika diperlukan:

...
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();
}

Lihat referensi YT Playables API selengkapnya.