آزمایشی YouTube Playables Flutter Web SDK

این راهنما نحوه استفاده از YouTube Playables SDK را با برنامه Flutter Web توضیح می دهد.

پیکربندی فلاتر

برنامه وب پیش‌فرض Flutter برای اینکه به‌عنوان یک قابل پخش کار کند، به تغییراتی نیاز دارد. تغییرات زیر از Flutter نسخه 3.24.5 مورد نیاز است.

به طور پیش فرض، Flutter برای بارگیری منابع از دایرکتوری ریشه پیکربندی شده است، در حالی که Playables نیاز دارد که منابع نسبت به نقطه ورودی بارگیری شوند. این یا به‌عنوان خطای 404 یا احتمالاً به‌عنوان خطایی که در کنسول جاوا اسکریپت نشان داده می‌شود نشان داده می‌شود که می‌خواند: Refused to execute script from... because its MIME type ('text/html') is not executable. یکی از راه‌های پیکربندی مجدد این است که برچسب زیر را از فایل index.html حذف کنید:

<base href="$FLUTTER_BASE_HREF">

به‌طور پیش‌فرض، Flutter برخی از کتابخانه‌ها را به‌جای جاسازی در برنامه به‌صورت پویا بارگیری می‌کند. CanvasKit یک نمونه از این موارد است. این به‌عنوان یک خطا در کنسول جاوا اسکریپت نشان داده می‌شود که می‌خواند: 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 استفاده می‌کند، و این به‌عنوان یک خطا در کنسول جاوا اسکریپت نشان داده می‌شود که می‌خواند: Refused to connect to '...' because it violates the following Content Security Policy directive: "connect-src 'self' blob: data". برای رفع این مشکل می توانید مراحل زیر را انجام دهید:

  • یک پوشه به نام "fonts" در ریشه برنامه وب flutter خود ایجاد کنید
  • خانواده فونت Roboto را از fonts.google.com دانلود کنید.
  • فونت ها را استخراج کنید و Roboto-Regular.ttf را در پوشه فونت کپی کنید
  • خط زیر را به pubspec.yaml برنامه وب flutter خود اضافه کنید:
  fonts:
    - family: Roboto
      fonts:
       - asset: fonts/Roboto-Regular.ttf

اطلاعات بیشتر در مورد پیکربندی فونت را می توان در مستندات Flutter یافت.

ادغام SDK

YouTube Playables SDK را می توان از یک بازی Flutter Web از طریق یک پوشش 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;

استفاده

  1. دستورالعمل ها را برای راه اندازی و مقداردهی اولیه وب SDK دنبال کنید.
    • web/index.html برنامه Flutter Web خود را به گونه ای تغییر دهید که برچسب های اسکریپت مورد نیاز را شامل شود.
  2. یک کپی از ytgame.dart را در src برنامه وب Flutter خود اضافه کنید.
  3. اطمینان حاصل کنید که بسته js به Flutter اضافه شده است، مانند اجرای دستور flutter pub add js .
  4. افزودن import 'src/location/of/ytgame.dart'; جایی که نیاز است
  5. از شی 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) در دارت را نشان می دهد:

...
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 را ببینید.