• 製品
  • 価格
  • リソース
  • サポート

モバイルアプリケーション・キープアライブ・ソリューション

オーディオ・ビデオの収集および再生に関わるモバイルアプリケーションの場合、通常は追加のキープアライブ処理が必要です。そうしないと、アプリケーションがバックエンドで実行されている際に一定の機能制限を受けるか、バックエンドでしばらく実行された後にシステムによって強制終了される可能性があります。以下では、Androidアプリケーション・キープアライブ・ソリューションiOSアプリケーション・キープアライブ・ソリューションをそれぞれ紹介します。

Androidアプリケーション・キープアライブ・ソリューション

現在、Android端末でよく使われるキープアライブ方法は、フロントエンドサービスを起動することです。フロントエンドサービスは特殊なサービスで、実行時に持続的な通知を表示し、ユーザーにサービスが実行中であることを知らせます。フロントエンドサービスの通知は持続的に表示されるため、システムはこれを高優先度のタスクと見なし、アプリケーションはバックエンドで継続的に実行され、システムによって終了されにくくなります。以下に、フロントエンドサービスの実現方法と手順を紹介します。

ステップ1:権限を宣言

AndroidManifest.xmlファイルに以下の権限宣言を追加します。
<!-- アプリケーションがフロントエンドサービスを使用することを許可 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

<!-- アプリケーションがフロントエンドサービスでカメラを使用する必要がある場合 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA" />

<!-- アプリケーションがフロントエンドサービスでマイクを使用する必要がある場合 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />

<!-- フロントエンドサービスが通知を送信することを許可 -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
注意:
プロジェクトのtargetSdkVersionが34以上に設定されており、かつフロントエンドサービスでカメラとマイクを使用する必要がある場合、FOREGROUND_SERVICE_CAMERAFOREGROUND_SERVICE_MICROPHONEの権限宣言が必須です。
Android 13以上では、フロントエンドサービスを通知バーに表示させたい場合、POST_NOTIFICATIONSの権限宣言が必要です。

ステップ2:サービスクラスを作成

Serviceを継承したクラスを作成し、その中でフロントエンドサービスのロジックを実現します。
public class MyForegroundService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}

@Override
public void onCreate() {
super.onCreate();
// 通知チャネルを作成
Notification notification = createNotification();
// サービス起動ロジックを処理
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(1024, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE);
} else {
startForeground(1024, notification);
}
}

private Notification createNotification() {
String CHANNEL_ONE_ID = "CHANNEL_ONE_ID";
String CHANNEL_ONE_NAME = "CHANNEL_ONE_ID";
NotificationChannel notificationChannel;
//8.0の判断を行います
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
notificationChannel = new NotificationChannel(CHANNEL_ONE_ID,
CHANNEL_ONE_NAME, NotificationManager.IMPORTANCE_HIGH);
notificationChannel.enableLights(true);
notificationChannel.setLightColor(Color.RED);
notificationChannel.setShowBadge(true);
notificationChannel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
if (manager != null) {
manager.createNotificationChannel(notificationChannel);
}
}
// 通知バーをクリックしてアプリケーションに戻るように設定、オプション
Intent intent = new Intent(this, MainActivity.class);
ActivityOptions options = null;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
options = ActivityOptions.makeBasic();
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
options.setPendingIntentBackgroundActivityStartMode(ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
}
PendingIntent pendingIntent;
if (options != null) {
pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE, options.toBundle());
} else {
pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE);
}

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Notification notification = new Notification.Builder(this, CHANNEL_ONE_ID).setChannelId(CHANNEL_ONE_ID)
.setSmallIcon(R.mipmap.videocall_float_logo)
.setContentTitle("これはテストタイトルです")
.setContentIntent(pendingIntent)
.setContentText("これはテスト内容です")
.build();
notification.flags |= Notification.FLAG_NO_CLEAR;
return notification;
}else {
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ONE_ID)
.setSmallIcon(R.mipmap.videocall_float_logo)
.setContentTitle("これはテストタイトルです")
.setContentText("これはテスト内容です")
.setContentIntent(pendingIntent)
.setPriority(NotificationCompat.PRIORITY_DEFAULT);
return builder.build();
}
}

@Override
public void onDestroy() {
super.onDestroy();
// フロントエンドサービスを停止
stopForeground(true);
}
}

ステップ3:サービスを宣言

AndroidManifest.xmlファイルでサービスを宣言します。
<service
android:name=".MyForegroundService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="mediaPlayback|mediaProjection|microphone|camera" />
注意:
android:foregroundServiceType属性を使用して、フロントエンドサービスが使用する必要のあるサービスタイプを指定し、バックエンドで正常なサービス機能を維持できるようにします。
mediaPlaybackサービスはメディア再生に使用。
mediaProjectionサービスはメディア投影に使用。
microphoneサービスはマイク有効化に使用。
cameraサービスはカメラ有効化に使用。

ステップ4:フロントエンドサービスを起動

フロントエンドサービスを起動する必要がある場合、必要に応じてフロントエンドサービスを起動します。
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
boolean areNotificationsEnabled = notificationManager.areNotificationsEnabled();
if (!areNotificationsEnabled) {
// ユーザーに通知権限を有効化するよう促します
Toast.makeText(this, "サービスが正常に動作するように通知権限を有効化してください", Toast.LENGTH_LONG).show();
// ユーザーを設定ページに案内します
Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName());
startActivity(intent);
} else {
// フロントエンドサービスを起動します
Intent serviceIntent = new Intent(this, MyForegroundService.class);
ContextCompat.startForegroundService(this, serviceIntent);
}
注意:
フロントエンドサービスが正常に動作するようにするため、フロントエンドサービスを起動する前に、通知権限が無効になっていないか確認することをお勧めします。無効になっている場合は、ユーザーに通知権限を有効化するよう促すことができます。

ステップ5:フロントエンドサービスを停止

外部コンポーネント(例:ActivityやBroadcastReceiver)からフロントエンドサービスを停止できます。
// サービスのIntentを作成
Intent serviceIntent = new Intent(this, MyForegroundService.class);

// サービスを停止
stopService(serviceIntent);
ほとんどのモバイルデバイスでは、フロントエンドサービスを起動したアプリケーションに対して、ユーザーが最近使用したアプリケーションのリストでスワイプして強制終了すると、フロントエンドサービスも同時に終了し、アプリケーションは完全に停止します。しかし、一部の海外ブランドのモバイルデバイス(例:Google Pixel シリーズやSAMSUNG A シリーズ)では、スワイプして強制終了した後もアプリケーションは完全に停止せず、フロントエンドサービスが依然としてアクティブな状態になり、ユーザーがメディア再生を聞き続けることができます
このようなデバイスでは、以下の2つのソリューションを実現することで、アプリケーションが強制終了された後もメディアオーディオが再生される現象を回避できます。

ソリューション1:サービス宣言でstopWithTask属性を定義

android:stopWithTask="true" 属性値を追加すると、タスクが削除された時にサービスは直ちに停止します。
<service
android:name=".MyForegroundService"
android:enabled="true"
android:exported="false"
android:stopWithTask="true"
android:foregroundServiceType="mediaPlayback|microphone" />

ソリューション2:Service 層で onTaskRemoved コールバックを監視

onTaskRemovedを監視し、タスクが削除されるとこのメソッドがコールバックされ、ここでクリーンアップ操作やデータ保存のロジックを実行できます。
@Override
public void onTaskRemoved(Intent rootIntent) {
super.onTaskRemoved(rootIntent);
// 例えば、ここでReal-Time Communication Engine (RTC Engine)の退室を実行し、オーディオの収集と再生を続けないようにします
TRTCCloud mTRTCCloud = TRTCCloud.sharedInstance(this);
mTRTCCloud.exitRoom();
}
注意:
上記の2つの方法のいずれかを選択してください。android:stopWithTask="true"を設定すると、onTaskRemovedメソッドはコールバックされなくなります。

iOSアプリケーション・キープアライブ・ソリューション

Appleは、ユーザーのプライバシーとデバイスのバッテリー寿命を保護するため、アプリケーションのバックエンドでの動作に厳しい制限を設けています。通常、特定のバックエンドモード(Background Modes)を有効化することで、ある程度アプリケーション・キープアライブを実現し、アプリケーションのバックエンドでのオーディオやビデオの再生を許可することができます。

バックエンドモードを有効化

Xcodeでバックエンドモード(Background Modes)を有効化する手順は以下の通り。
1. Xcodeプロジェクトを開きます。
2. プロジェクトファイル(通常はプロジェクトナビゲータの上部にあります)を選択します。
3. プロジェクトファイルでターゲット(Targets)を選択します。
4. ターゲット設定で、Signing&Capabilitiesタブを選択します。
5. Background Modesオプションを見つけて、Audio, AirPlay, and Picture in Pictureモードにチェックを入れます。




よくある質問

1. 音声通話またはライブストリーミングシーンで、配信者がアプリケーションをバックエンドに移動したり画面をロックしたりした場合、イヤホンの抜き差しによりオーディオの収集と再生が無音になります
SDKのデフォルトのオーディオポリシーでは、システム音量タイプは自動切り替えモード、即ち「通話中は通話音量、非通話中はメディア音量」になっています。また、音量タイプはオーディオルートの変化に伴って変化します。例えば、イヤホンを挿入すると、音量タイプは通話音量からメディア音量に切り替わります。したがって、自動切り替えモードでは、配信者がイヤホンを抜き差しすると、システム音量タイプが切り替わり、この時、システムはオーディオドライバーを再起動する必要があります。しかし、iOSシステムはバックエンドや画面ロック状態でオーディオドライバーを再起動する際、失敗する可能性があるため、オーディオの収集と再生が無音になる原因となります。
このような問題に対しては、システムの音量タイプを固定することで回避できます。例えば、全過程の通話音量または全過程のメディア音量を指定します。
// 全過程の通話音量を指定
[self.trtcCloud setSystemVolumeType:TRTCSystemVolumeTypeVOIP];

// 全過程のメディア音量を指定
[self.trtcCloud setSystemVolumeType:TRTCSystemVolumeTypeMedia];
2. ビデオ通話またはライブストリーミングシーンで、配信者がアプリケーションをバックエンドに移動したり画面をロックしたりした場合、リモート視聴者がストリームを受信すると、画面はブラックアウトするが、オーディオは正常です
Appleシステムは、アプリケーションがバックエンドでビデオを収集することを厳しく禁止しています。バックエンドモードを有効化した場合でも、アプリケーションがバックエンドに移行するとカメラは自動的に停止します。これはユーザーのプライバシーを保護し、アプリケーションがユーザーの同意なしにビデオを録画するのを防ぐためです。したがって、このようなシーンでのビデオ収集の問題は一時的に回避できず、オーディオの正常な収集と再生のみを実現できます。
3. 視聴者がルームに入室した際にルーム内で配信者がおらず、アプリケーションをバックエンドに移動したり画面をロックしたりすると、後続でルーム内で配信者が現れても正常に受信できません
iOSアプリケーションがバックエンドに切り替わる前に、AudioUnitの収集または再生を開始していない場合、すぐに中断され、フロントエンドに切り替わるまで人為的にウェイクアップすることはできません。このような問題を解決するには、アプリケーションがバックエンドに切り替わる前に、AudioUnitを常に実行状態に保つ(無音データを再生)だけで十分です。具体的な実現方法は、以下のサンプルコードをご参照ください。
// ルームに入室後にカスタムオーディオトラックを有効化します
[self.trtcCloud enableMixExternalAudioFrame:NO playout:YES];

// ルームから退室する前にカスタムオーディオトラックを無効化します
[self.trtcCloud enableMixExternalAudioFrame:NO playout:NO];

ビデオピクチャーインピクチャー アプリケーション・キープアライブ・ソリューション

ビデオピクチャーインピクチャー機能により、アプリケーションのビデオを常にフロントエンドで再生しつつ、アプリケーション・キープアライブを実現できます。具体的な実現方法については、ビデオピクチャーインピクチャーソリューションをご参照ください。