يوضّح هذا الدليل كيفية استخدام حزمة تطوير البرامج (SDK) لتطبيقات YouTube Playables مع تطبيق ويب مكتوب بلغة Flutter.
إعدادات Flutter
يتطلب تطبيق الويب التلقائي من Flutter إجراء بعض التغييرات لكي يعمل كتطبيق قابل للتشغيل. إنّ التغييرات التالية مطلوبة اعتبارًا من الإصدار 3.24.5 من Flutter.
يتم ضبط 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 من لعبة على الويب باستخدام Flutter من خلال حزمة تفاعل JavaScript مشابهة لهذه الحزمة:
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 Web لكي يتضمّن علامات النصوص البرمجية المطلوبة.
- عدِّل
- أضِف نسخة من
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 default
هي إضافة المكالمات إلى ملف 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();
}
اطّلِع على مرجع YT Playables API الكامل.