Hướng dẫn này giải thích cách sử dụng SDK YouTube Playables với ứng dụng Flutter Web.
Cấu hình Flutter
Ứng dụng web Flutter mặc định cần một số thay đổi để hoạt động như một ứng dụng có thể chơi. Bạn bắt buộc phải thực hiện các thay đổi sau kể từ Flutter phiên bản 3.24.5.
Theo mặc định, Flutter được định cấu hình để tải tài nguyên từ thư mục gốc, whereas Playables requires that resources are loaded relative to the entry
point. Lỗi này sẽ xuất hiện dưới dạng lỗi 404 hoặc có thể là lỗi xuất hiện trong bảng điều khiển javascript có nội dung: Refused to execute script
from... because its MIME type ('text/html') is not executable.
Một cách để định cấu hình lại lỗi này là xoá thẻ sau khỏi tệp index.html:
<base href="$FLUTTER_BASE_HREF">
Theo mặc định, Flutter sẽ tải một số thư viện một cách linh động thay vì nhúng các thư viện đó vào ứng dụng. CanvasKit là một ví dụ về điều này. Lỗi này sẽ xuất hiện dưới dạng lỗi trong bảng điều khiển JavaScript có nội dung: Refused to connect to '...' because it
violates the following Content Security Policy directive: "connect-src 'self'
blob: data:".
Flutter cũng có thể xuất ra các nhật ký bổ sung khi không tải được, chẳng hạn như Failed to download any of the following CanvasKit URLs
. Để khắc phục vấn đề này, bạn có thể tạo ứng dụng web bằng cờ bổ sung sau:
$ flutter build web --no-web-resources-cdn
Theo mặc định, các ứng dụng web Flutter sẽ tải phông chữ một cách linh động thay vì nhúng phông chữ vào ứng dụng. Ứng dụng Flutter mặc định sử dụng phông chữ Roboto và lỗi này sẽ xuất hiện dưới dạng lỗi trong bảng điều khiển javascript có nội dung: Refused to connect to '...'
because it violates the following Content Security Policy directive:
"connect-src 'self' blob: data".
Để khắc phục lỗi này, bạn có thể làm theo các bước sau:
- Tạo một thư mục có tên "fonts" (phông chữ) trong thư mục gốc của ứng dụng web flutter
- Tải bộ phông chữ Roboto xuống từ fonts.google.com.
- Giải nén phông chữ và sao chép Roboto-Regular.ttf vào thư mục phông chữ
- Thêm dòng sau vào pubspec.yaml của ứng dụng web flutter:
fonts:
- family: Roboto
fonts:
- asset: fonts/Roboto-Regular.ttf
Bạn có thể tìm thêm thông tin về cấu hình phông chữ trong tài liệu về Flutter.
Tích hợp SDK
Bạn có thể sử dụng SDK YouTube Playables từ một trò chơi trên Flutter Web thông qua trình bao bọc tương tác JS tương tự như trình bao bọc này:
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;
Cách sử dụng
- Làm theo hướng dẫn để thiết lập và khởi chạy SDK web.
- Sửa đổi
web/index.html
của ứng dụng Flutter Web để thêm các thẻ tập lệnh bắt buộc.
- Sửa đổi
- Thêm một bản sao của
ytgame.dart
vàosrc
của ứng dụng web Flutter. - Đảm bảo rằng gói js được thêm vào Flutter, chẳng hạn như bằng cách chạy lệnh
flutter pub add js
. - Thêm
import 'src/location/of/ytgame.dart';
khi cần. - Sử dụng đối tượng
ytgame
để truy cập vào SDK.
Ví dụ
Dưới đây là một số ví dụ về cách hoạt động của API Dart.
firstFrameReady
và gameReady
Để trò chơi bắt đầu chính xác, bạn cần gọi firstFrameReady
khi khung hình đầu tiên đã sẵn sàng hiển thị và gameReady
khi trò chơi đã sẵn sàng để tương tác. Một cách để thực hiện việc này trong ứng dụng Flutter mặc định là thêm các lệnh gọi vào tệp main.dart:
...
import 'src/ytgame.dart';
...
void main() {
ytgame.game.firstFrameReady();
ytgame.game.gameReady();
runApp(const MyApp());
}
Xin lưu ý rằng đây chỉ là một ví dụ để chạy trò chơi của bạn – bạn sẽ cần đặt vị trí của các lệnh gọi này đúng cách để triển khai đúng cách.
sendScore
Ví dụ này cho thấy cách triển khai sendScore(int points)
trong 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
Đây là ví dụ về cách một trò chơi có thể nghe các sự kiện Pause
đến từ YT Playables để tạm dừng công cụ của trò chơi khi cần:
...
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();
}
Xem tài liệu tham khảo đầy đủ về API Nội dung có thể phát trên YouTube.