使用 Amazon GameLift Streams 构建移动端串流体验
Building a mobile streaming experience with Amazon GameLift Streams
移动游戏串流意味着玩家无需通过应用商店下载应用,即可在任何手机或平板上通过浏览器链接直接进入游戏。在本系列的第一部分中,我们使用Amazon GameLift Streams React Starter 示例构建了一个针对桌面优化的串流前端。借助该方案,玩家可以在桌面浏览器中通过键盘和鼠标输入来串流游戏内容。那么移动端呢?
游戏串流取消了玩家在任何设备上的传统下载和安装步骤。在移动设备上,这意味着玩家可以直接通过浏览器链接进入游戏,而无需经过应用商店。然而,与桌面端相比,为移动端构建串流前端会带来另一组不同的挑战:触摸输入取代了键盘和鼠标,某些游戏类型可能需要强制锁定为横屏方向,且移动设备上的平台 API 行为与其桌面端对应 API 不同。
在本文中,我们将介绍构建专用移动前端的关键考量,该前端在解决上述挑战的同时,复用第一部分中现有的后端基础设施和 AWS 云开发工具包(AWS CDK) 部署模式。
考虑构建专用的移动端前端
当你最初考虑面向移动设备用户时,响应式设计实践是一条强有力的路线。媒体查询、布局框架和浏览器内置属性可以简化这一流程。
然而,对于游戏串流,这种方法很快就会失效。桌面端前端是围绕键盘和鼠标输入、窗口模式以及桌面浏览器 API 构建的。这些概念无法直接移植到移动设备上。你需要叠加在串流画面上的基于触摸的虚拟控件,以及强制横屏方向。你还需要针对某些移动平台未实现的 API 寻找替代方案。
一种方法是创建一个独立的项目(mobile-frontend/),连接到相同的后端 API。这样可以让两个前端各自保持专注。桌面版继续针对其输入模型进行优化,移动版则可以就触控、方向和全屏模式做出明确的设计决策,而无需通过条件分支使桌面代码变得复杂。
如何使用渐进式 Web 应用让浏览器消失
在移动端需要解决的第一个问题是浏览器用户界面(UI)。地址栏、导航按钮和状态栏会占用你的流媒体所需的屏幕空间。以独立模式运行的渐进式 Web 应用(PWA)可以移除所有这些元素,而无需提交到应用商店。
首先,创建一个 Web 应用清单(manifest),指定浏览器在安装后应如何运行:
{
"display": "standalone",
"orientation": "landscape",
"background_color": "#000000",
"theme_color": "#000000"
}
设置 display: standalone 会在用户将你的应用添加到主屏幕时移除浏览器界面。orientation: landscape 字段在 Android 上锁定显示方向。使用黑色背景和主题色,以便系统界面在加载期间与你的流媒体内容融为一体。对于 iOS 有一个重要的注意事项:截至本文撰写时,Safari 完全忽略清单中的 orientation 字段。你无法在 iOS 上以编程方式锁定屏幕方向。取而代之的是,检测设备处于竖屏模式时显示一个提示,要求玩家旋转设备。你可以使用 CSS 变换来旋转视口,但这会破坏触摸事件与流媒体游戏内容之间的坐标映射。玩家的点击会落在错误的位置。你还需要一个 Service Worker 来满足 PWA 安装要求。由于流媒体应用没有离线使用场景,请保持其最小化:一个不缓存任何内容的直通处理,运行 skipWaiting() 和 clients.claim(),意味着 Web 应用在离线时可以优雅地降级。下图是移动端前端作为已安装的 PWA 在 iPad 上运行的示例,它隐藏了浏览器界面,提供完全沉浸式的流媒体体验:
图 1:以已安装 PWA 形式在 iPad 上运行的移动端前端
iOS 的变通方案
iOS Safari 对基于浏览器的游戏串流提出了独特的考量。移动 Safari 在某些桌面浏览器广泛支持的 Web API 上的行为有所不同。以下是在这些限制条件下可行的实用方法:
在不使用全屏 API 的情况下实现全屏
在桌面端,你调用 Element.requestFullscreen(),浏览器会处理其余部分。在 iOS 上,对 video 元素调用 fullscreen 会导致 Safari 切换到其自带的视频播放器。该播放器完全脱离了浏览器上下文。在这种情况下,你无法在其上方渲染任何 HTML 覆盖层,这对游戏串流来说是个问题,因为你需要在视频上层叠加触控控件、UI 元素和状态指示器。取而代之的是,无条件地将 position: fixed; inset: 0 应用于 body 元素。这会在你的应用加载的那一刻就填满整个视口,同时保持你的 HTML 层完整以用于覆盖层。结合 PWA 独立模式使用时,没有浏览器 UI 与之竞争,你的串流将占据每一个像素。要在现代 iPhone 上将内容延伸到刘海屏后方,请在你的 viewport meta 标签中添加 viewport-fit: cover。这会告诉 Safari 让你的内容流入安全区域缩进。如有需要,你可以使用 CSS env(safe-area-inset-*) 值进行管理。
解锁音频播放
iOS 会阻止音频自动播放,直到用户执行手势操作。如果尝试在没有先前点击的情况下以编程方式播放音频,浏览器会静默忽略该请求。解决方案是在用户点击"开始流"的同一事件处理程序中调用 audioElement.play()。这一次点击既启动了流,又满足了浏览器的自动播放策略。这里有一个微妙之处:在点击发生时,SDK 可能尚未附加其音频轨道。为处理此问题,还需在音频元素上监听 loadedmetadata 事件,并在该事件触发时再次调用 play()。这样可以捕获音频轨道在初始手势之后才到达的情况。GameLift Streams SDK 要求你在其构造函数中传入独立的 <video> 和 <audio> 元素。保持视频元素静音,让音频通过其专用元素播放。这种分离使得基于手势的解锁成为可能,因为你可以独立于视频来解锁音频元素。
正确检测 iPad
如今,现代 iPad 将 navigator.platform 报告为 MacIntel 而非 iPad。这使得仅通过用户代理无法将它们与 Mac 区分开来。检测 iPad 的一种方法是检查 navigator.platform === 'MacIntel' 和 navigator.maxTouchPoints > 1. 的组合。Mac 笔记本电脑报告零个或一个触摸点,而 iPad 报告的更多。检测之后,应用相同的 iOS 变通方案:PWA 独立模式提示、基于手势的音频解锁以及基于 CSS 的全屏。
添加虚拟手柄控制
如果没有物理手柄,你的玩家需要屏幕上的控制按钮来与串流游戏进行交互。Amazon GameLift Streams Web SDK 接受符合 W3C Gamepad 接口 的自定义手柄对象,这意味着你可以构建一个虚拟手柄,SDK 会将其与物理手柄同等对待。以下截图展示了一个手柄覆盖层的示例:
图 2:虚拟手柄控制器覆盖层
要实现此功能:
-
- 创建一个实现 Gamepad 接口的类。它需要
axes(一个包含四个值的数组,用于双摇杆)、buttons(一个包含 17 个按钮对象的数组,对应标准手柄布局),以及一个connected属性。使用 200 或更高的索引,以避免与占用索引 0 到 3 的物理控制器发生冲突。
- 创建一个实现 Gamepad 接口的类。它需要
-
- 在流连接后将其注册到 SDK。在 WebRTC 连接建立且输入已附加后,调用
addGamepad():
sdkRef.current.addGamepad(gamepadRef.current);
- 在流连接后将其注册到 SDK。在 WebRTC 连接建立且输入已附加后,调用
- 在每次输入变化时调用
processGamepads()。每当触摸事件更新了摇杆轴或按钮状态时,运行processGamepads()将当前状态传输到流实例。SDK 不会自动轮询你的手柄,因此你必须在状态发生变化时通知它。
该方法与 SDK 配置中的 autoGamepad: true 共存,后者会自动检测并转发物理蓝牙或 USB 控制器。连接了物理手柄的玩家可以在虚拟控制之外或代替虚拟控制使用它,无需任何额外代码。
复用后端基础设施
在第一篇文章中,我们已经有一个 CDK 堆栈,用于部署桌面前端和一个管理流媒体会话的 API,其中包括一个 Amazon Simple Storage Service(Amazon S3)存储桶、一个 Amazon CloudFront 分发以及一个 AWS WAF WebACL。为了避免重复该堆栈,我们使用 buildAssetPath 属性对其进行参数化,该属性指向您想要部署的任意前端构建目录:
// Desktop frontend
new AmazonGameliftStreamsReactStarterFrontendStack(app,
'AmazonGameliftStreamsReactStarterFrontendStack', {
buildAssetPath: './amazon-gamelift-streams-react-starter-frontend/build',
env: { account, region: 'us-east-1' }, // WAF requires us-east-1
});
// Mobile frontend
new AmazonGameliftStreamsReactStarterFrontendStack(app,
'AmazonGameliftStreamsReactStarterMobileFrontendStack', {
buildAssetPath: './mobile-frontend/build',
env: { account, region: 'us-east-1' }, // WAF requires us-east-1
});
每次实例化都会创建具有相同安全态势的独立资源:地理限制、速率限制和 IP 信誉规则。两者都必须部署到 us-east-1,因为 Amazon CloudFront 分发服务的 AWS WAF WebACL 只能存在于该区域。
先决条件
在部署移动前端之前,您需要:
- 一个具有部署 CDK 堆栈权限的 AWS 账户
- 来自第一部分的 API 堆栈已部署(Amazon Cognito用户池和 API Gateway 端点)
- 已安装 Node.js v18 或更高版本及 npm
- 已安装 AWS CDK CLI(
npm install -g aws-cdk) - 已使用目标账户的凭据配置 AWS CLI
- 已从入门页面下载 Amazon GameLift Streams Web SDK
部署移动前端
以下步骤假设您已按照第一部分中的说明部署了 API 堆栈并配置了 Amazon Cognito。
- 将 Amazon GameLift Streams Web SDK 文件复制到
mobile-frontend/src/gamelift-streams-websdk/ - 在
mobile-frontend/src/App.tsx中更新 Amplify 配置,填入 API 堆栈输出中的 Amazon Cognito 用户池 ID、客户端 ID 和 API 端点 - 在 mobile-frontend/ 目录中运行
npm install && npm run build - 运行
cdk deploy AmazonGameliftStreamsReactStarterMobileFrontendStack
您的移动前端连接到与桌面版本相同的 Amazon Cognito 用户池和 Amazon API Gateway 端点。无需额外的后端资源。
您现在应该拥有一个如图所示的移动流媒体应用程序:
图 3:移动流媒体应用程序
清理资源
为避免持续产生费用,在不再需要时删除移动前端堆栈:
cdk destroy AmazonGameliftStreamsReactStarterMobileFrontendStack这将删除由该堆栈创建的 Amazon CloudFront 分发、Amazon S3 存储桶和 AWS WAF WebACL。如果您还想删除桌面前端和 API,请运行 cdk destroy --all。
如果您使用 AWS 管理控制台 而非 CDK 删除堆栈,则必须先删除 Amazon CloudFront 分发(位于 us-east-1),才能移除 WAF WebACL。
Amazon GameLift Streams 容量在分配时会产生费用。在不使用时,请将流组中的常驻容量和按需容量均设置为零。有关详细信息,请参阅 Amazon GameLift Streams 定价页面。
结论
将游戏串流交付到移动设备会带来超越响应式设计的方案特定挑战。通过构建一个带有虚拟手柄控制的专用移动前端 PWA,你可以提供以触控为先的体验,该体验可在任何带有浏览器的手机或平板电脑上运行,且无需提交应用商店。同样的模式可延伸至其他支持浏览器的设备,例如智能电视或机顶盒,这些设备的输入方式和显示限制与桌面端不同,但串流后端保持不变。
移动端前端实现可在 Amazon GameLift Streams React Starter 示例仓库 的 mobile-frontend/ 目录下找到。克隆该仓库,按照 README 中的部署步骤操作,即可开始将游戏内容流式传输到移动设备。
来源:AWS for Games · 游戏技术与运营 · aws.amazon.com
