開発者コンソール

Amazon Fire TVおよびFireタブレット対応のストリーミング/非ストリーミングアプリの収益化

Share:
Make Money Fire tablet Fire TV
Blog_Header_Post_Img

Amazonデバイス向けアプリを開発するにあたり、Amazon FireタブレットとAmazon Fire TVでは収益化の方法が大きく異なります。実際、Fire TV対応アプリはリモコン操作とリーンバック視聴に対応しているのに対し、Fireタブレット対応アプリはタッチ操作と外出先での使用に適するよう設計されています。ただ、収益化を考える際は、デバイスの種類ではなく、アプリの種類で分類する方が合理的です。

 

動画ストリーミングアプリのユーザーと非ストリーミングアプリ(ゲーム、ユーティリティ、教育ツールなど)のユーザーでは、ニーズや期待、行動が基本的に異なります。ストリーミングアプリは、通常、継続的かつ受動的なエンゲージメントを目的とし、ユーザーが主にリモコン操作で長編動画コンテンツの検索し、視聴できるように設計されています。一方、非ストリーミングアプリは、対話型でタスク指向です。タップを繰り返してゲームのレベルを上げる、やることリストにチェックを入れる、パズルを解くなどの用途がその例です。

 

この違いは、収益化戦略に直接影響します。この記事では、一般的な収益化の方法を見直して、開発するアプリのタイプに合わせてシナリオを検討し、さらに、アプリの統計データレポートに基づいて収益とリテンションを最適化する方法を紹介します。

一般的な収益化の方法

具体的な実装例を掘り下げる前に、Amazonアプリストアに用意されている収益化オプションについて理解しておきましょう。アプリごとに固有のニーズはあるものの、ほとんどの収益化戦略は以下のいずれかに分類されます。

アプリ内課金(IAP)

アプリ内課金は、最も柔軟性の高い収益化オプションの1つです。アプリ内課金の種類は以下の通りです。

 

  • 消費型アイテム: 追加ライフ、追加アクション、ゲーム内で使用する通貨など、アプリ内で作成され、消費されるもの。複数回購入できます。
  • 非消費型アイテム: 1回だけ購入して、アプリ内またはゲーム内の機能やコンテンツの制限を解除(アンロック)できるもの。
  • 定期購入型アイテム: プレミアムコンテンツやプレミアム機能を定額料金で提供するもの。

 

Amazon IAPの統合は、ストリーミングアプリ、非ストリーミングアプリのどちらでも機能します。

アプリ内広告

広告は、サードパーティ広告サービスのサポート対象SDKを介して統合することができます。これには、バナー広告、リワード動画、インタースティシャル動画、プリロール動画などの形式があります。このオプションは、ユーザーが頻繁に利用する無料アプリに最適です。

有料アプリ

IAPによる収益化ではなく、アプリのインストール時に前払いで料金を請求します。これは、アプリの魅力が明確で、提供中のコンテンツの更新や追加購入が不要な場合に最適です。ただし、アプリにアンロック可能なコンテンツや繰り返し利用できるコンテンツが含まれている場合は、IAPの方がより適している可能性があります。

Merch on Demand

Merch on Demandでは、オリジナルの商品(シャツ、マグカップ、アクセサリーなど)をアプリで直接販売できます。これは、ブランドエンゲージメントの構築に役立ちます。ファンやニッチなオーディエンスを持つアプリの場合、特に効果的です。

商品広告

Product Advertising API(英語のみ)を使用して、アプリをAmazonのリテールカタログにリンクさせることもできます。物理的な商品に関連しているアプリ(フィットネス、料理、テクノロジーなど)に効果的で、関連商品リストの表示やリファラル収益の獲得が可能です。

 

これを利用するには、Amazonアソシエイトアカウントを所有していて、かつアプリが要件を満たしている必要があります。

収益化の実践: 4つの実用的なシナリオ

アプリを収益化するとはどういうことでしょうか。 ここでは、ストリーミングアプリと非ストリーミングアプリでのアプリ内課金(IAP)とそれ以外の実装方法を示す4つの実用的なシナリオを紹介します。

 

各例には、簡単なユースケース、その方法が適している理由、必要となる主な実装手順が含まれています。有料アプリ、アプリ内課金、広告のどの方法を選択する場合でも、これらのパターンを理解しておけば、収益化計画の策定に役立てて、自信を持ってコーディングに取り掛かれるはずです。

 

シナリオ1: ストリーミングアプリでのIAPの定期購入型アイテム

Fire TVとFireタブレットの両方で提供するフィットネス系のストリーミングアプリを開発しており、そのアプリにはワークアウト動画のカタログが含まれています。一部のコンテンツは無料ですが、複数週にわたるプログラムや特別なトレーニングシリーズなどのプレミアムワークアウトプランを利用できるのは有料定期購入ユーザーのみです。利用制限を解除する場合は、ユーザーがAmazonアプリストアのIAP設定を使用して直接アプリで定期購入します。

この方法が適している理由

ストリーミングアプリは、新しいコンテンツを定期的にチェックしながら長期間にわたって視聴するユーザーに人気があります。ガイド付きワークアウト、ウェルネスシリーズ、教育コースなどのように、コンテンツが専門的であったり頻繁に更新される場合、定期購入型アイテムは、プレミアム機能へのアクセスを制御しながら、ユーザーの継続利用を促進できます。制限のある無料コンテンツは、もっと楽しみたいというユーザーの欲求を刺激し、定期購入を促すのに役立ちます。

主な実装手順

Amazonのアプリ内課金(IAP)SDKを使用すると、Fire TVとFireタブレットのどちらにも対応する定期購入ベースのアクセスを提供できます。定期購入ベースのアプリの収益化はAmazonアプリストアによって管理されており、開発者側で課金と更新が簡単にできます。

 

まず、開発者コンソールで定期購入型アイテムのSKUを定義する必要があります。アプリを管理している場合、[アプリ内課金(IAP)アイテム] タブに移動し、[新しいIAPを追加] をクリックします。

Monetizing streaming. In-app item screenshot

定期購入型アイテムのタイトルとSKUを指定し、[確認] をクリックします。

Monetizing streaming. Create new subscription screenshot

次に、アプリのコードでAppstore SDK IAPを実装します。Amazonとのアプリ内課金に関する通信は、主にPurchasingServicePurchasingListener(英語のみ)を使用します。

 

その後、AndroidManifest.xmlファイルにResponseReceiver(英語のみ)を追加します。例えば次のようになります。

Copied to clipboard
<application>
…
  <receiver
      android:name="com.amazon.device.iap.ResponseReceiver"
      android:permission="com.amazon.inapp.purchasing.Permission.NOTIFY"
      android:exported="true">
    <intent-filter>
      <action android:name="com.amazon.inapp.purchasing.NOTIFY" />
    </intent-filter>
  </receiver>
</application>

MainActivity.ktファイルのonResumeメソッド内で、PurchasingListener実装をPurchasingServiceで登録します。次に、ユーザー固有のデータをgetUserDataで取得し、アクティブな定期購入型アイテムをgetPurchaseUpdatesで復元します。この後者の手順は、アプリを再インストールする場合やユーザーが新しいデバイスから定期購入型アイテムにアクセスする場合に必要です。

Copied to clipboard
override fun onCreate(savedInstanceState: Bundle?) {
  …
  myListener = MyPurchasingListener()
  …
}

…

override fun onResume() {
  super.onResume()
  PurchasingService.registerListener(this.applicationContext, myListener)
  PurchasingService.getUserData()
  PurchasingService.getPurchaseUpdates(false)
}

PurchasingListenerインターフェイスでは、Amazonアプリストアからの非同期応答を処理するために複数のハンドラーを実装する必要があります。最も重要なのは、onUserDataResponseonProductDataResponseonPurchaseResponseonPurchaseUpdateResponseの実装です。

Copied to clipboard
class MyPurchasingListener : PurchasingListener {

  override fun onUserDataResponse(response: UserDataResponse) {
    // [PurchasingService.getUserData] に応答して呼び出されます。
  }

  override fun onProductDataResponse(response: ProductDataResponse) {
    // [PurchasingService.getProductData] に応答して呼び出されます。
  }

  override fun onPurchaseResponse(response: PurchaseResponse) {
    // [PurchasingService.purchase] に応答して呼び出されます。
  }

  override fun onPurchaseUpdatesResponse(response: PurchaseUpdatesResponse)
  {
    // [PurchasingService.getPurchaseUpdates] に応答して呼び出されます。
  }

たとえば、getProductData呼び出しの処理には以下が必要です。

Copied to clipboard
override fun onProductDataResponse(response: ProductDataResponse) {
  val requestStatus: ProductDataResponse.RequestStatus = response.requestStatus
  when (requestStatus) {
    ProductDataResponse.RequestStatus.SUCCESSFUL -> {
      Log.i(TAG, "ProductDataResponseが成功しました。")
      response.productData.forEach { sku, product ->
        Log.d(TAG, "商品: SKU:$sku、タイプ:${product.productType}、タイトル:${product.title}、説明:${product.description}、価格:${product.price}、SmallIconUrl:${product.smallIconUrl}")
        // 商品情報を使用してアプリのUIを更新します。
      }

      // リクエストされたSKUに利用できないものがないかチェックします。
      val unavailableSkus: Set<String> = response.unavailableSkus
      if (unavailableSkus.isNotEmpty()) {
        Log.w(TAG, "利用できないSKU:$unavailableSkus。これらのSKUは無効であるか、Amazon開発者コンソールで正しく公開されない可能性があります。")
      }
      
      // このデータに基づいて、購入可能な商品の購入ボタンを有効にします。
    }
    ProductDataResponse.RequestStatus.FAILED -> {
      Log.e(TAG, "ProductDataResponseが失敗しました。商品情報を取得できませんでした。")
    }
    ProductDataResponse.RequestStatus.NOT_SUPPORTED -> {
      Log.w(TAG, "ProductDataResponseはサポートされていません。IAPを利用できない可能性があります。")
    }
    else -> {
      Log.w(TAG, "ProductDataResponse: 未処理ステータス:$requestStatus")
    }
  }
}

PurchasingService.purchaseに対する呼び出しが作成されたら、onPurchaseResponseハンドラーを使用して、完了した購入についての情報の取得と購入の検証を行い、プレミアムコンテンツへのユーザーのアクセスを許可します。

Copied to clipboard
override fun onPurchaseResponse(response: PurchaseResponse) {
    val requestStatus: PurchaseResponse.RequestStatus = response.requestStatus
    when (requestStatus) {
      PurchaseResponse.RequestStatus.SUCCESSFUL -> {
        val receipt: Receipt? = response.receipt
        if (receipt != null) {
          Log.i(TAG, "PurchaseResponseが成功しました。レシートID:${receipt.receiptId}、SKU:${receipt.sku}、購入日:${receipt.purchaseDate}、キャンセル日:${receipt.cancelDate}")

          // 重要: ここでサーバー側のレシート検証を実行します。

          // プレミアムコンテンツへのアクセスをユーザーに許可するためのロジックを実装します。
          Log.i(TAG, "SKU${receipt.sku}の購入が正常に完了しました。")

          if (receipt.isCanceled) {
            // このステートは、購入が(たとえばAmazonサポートによって)無効化されたことを示します。これは、正常に完了した新規の購入ではまれなケースです。
            Log.w(TAG, "onPurchaseResponseで購入がキャンセル済みとマークされました:${receipt.receiptId}。RVSでキャンセルが確認されたら、アクセス権を取り消す必要があります。")
            // プレミアムコンテンツへのアクセスを取り消すためのロジックを実装します。
          }
        } else {
          Log.e(TAG, "PurchaseResponseはSUCCESSFULですが、レシートがnullです。このような状況は改善が必要です。")
      }
    }
    PurchaseResponse.RequestStatus.FAILED -> {
      // 購入の試行に失敗しました(例:支払いの問題、ユーザーによるキャンセル)。
      Log.e(TAG, "PurchaseResponseが失敗しました。アクセス権は付与されません。")
      
      // 購入できなかったことをユーザーに伝えます。
      // オプションとして、理由または対処方法を表示します。
    }
    PurchaseResponse.RequestStatus.ALREADY_PURCHASED -> {
      // このステータスは、ユーザーがこのアクティブな定期購入型アイテムを既に持っていることを意味します。
      // これは必ずしもエラーであるとは限りません。
      // ユーザーがコンテンツにアクセスできることを確認します。
      Log.i(TAG, "PurchaseResponse: SKU${response.receipt?.sku}は購入済みです。")

      // 定期購入型アイテムがアクティブであることを確認します。
      // ローカルキャッシュの確認や、getPurchaseUpdatesまたはレシート検証サービスでの再検証が必要な場合もあります。

      // 必要に応じてUIを更新し、ステータスを反映させます。
    }
    PurchaseResponse.RequestStatus.INVALID_SKU -> {
      // 購入の呼び出しで示されたSKUが無効であるか、Amazonで認識できません。
      Log.e(TAG, "PurchaseResponse: SKUが無効です。SKU:${response.receipt?.sku}。Amazon開発者コンソールでのSKUの定義とアプリのコードを確認してください。")
    }
    PurchaseResponse.RequestStatus.NOT_SUPPORTED -> {
      // この購入操作はサポートされていません。
      Log.w(TAG, "PurchaseResponseはサポートされていません。IAPを利用できない可能性があります。")
    }
    else -> {
      Log.w(TAG, "PurchaseResponse: 未処理ステータス:$requestStatus")
    }
  }
}

レスポンスステータスがSUCCESSFULの場合、レシート検証サービス(RVS)で購入を検証する必要があります。レシートオブジェクト(response.receipt)には、購入トークン(receipt.receiptId)やその他の詳細が含まれています。この情報は、バックエンドサーバーに送信される必要があります。バックエンドサーバーは、この情報に基づいて、Amazonでの購入をRVSで検証します。これによりクライアント側での改ざんを防ぎ、購入が正当であることを確認することができます。

 

購入を検証するためのサーバー側のロジックでは以下が実行されます。

1. アプリがreceipt.receiptIdresponse.userData.userIdを安全なバックエンドサーバーに送信します。

2. 開発者の共有シークレット、ユーザーのユーザーID、購入レシートIDに基づくパスを使用して、サーバーがRVSを呼び出します。例えば次のようになります。

Copied to clipboard
https://appstore-sdk.amazon.com/version/1.0/verifyReceiptId/developer/REPLACE-WITH-SHARED-SECRET/user/REPLACE-WITH-USER-ID/receiptID/REPLACE-WITH-RECEIPT_ID

 

3. RVSのレスポンスが正常に行われたら、購入および定期購入のステータスをデータベースに記録し、receipt.startDatereceipt.endDateを確実に保存します。

4. プレミアムコンテンツをアンロックするために、アプリに(セキュアなAPIまたはプッシュ通知で)信号を送ります。

 

MainActivityの定義内で、PurchasingService.getProductDataを呼び出し、特定の定期購入型IAPアイテムに関する情報をそのSKUに基づいて取得します。例えば次のようになります。

Copied to clipboard
fun fetchSubscriptionProductData() {
  Log.d(TAG, "定期購入型アイテムの商品データを取得します。")
  val subscriptionSkus: MutableSet<String> = HashSet()
  subscriptionSkus.add("subscription_mo_ptp_01")
  subscriptionSkus.add("subscription_yr_ptp_02")
  PurchasingService.getProductData(subscriptionSkus)
}

購入の開始は次のようになります。

Copied to clipboard
fun initiatePurchase(sku: String) {
  if (sku.isBlank()) {
    Log.e(TAG, "SKUを空白にして購入を開始することはできません。")
    return
  }
  Log.d(TAG, "SKU: $skuの購入を開始しようとしています")
  PurchasingService.purchase(sku)

ストリーミングアプリの収益化において、定期購入アイテムの実装は、技術的な負担も少なく、戦略的にも望ましい選択となります。動画コンテンツを楽しみたいというユーザーの自然な行動パターンに適しており、開発者側もユーザーの具体的なニーズに基づく安定した収入を確保できます。

 

シナリオ2: 非ストリーミングアプリでのIAPの消費型アイテムと非消費型アイテム

Fireタブレット向けのカジュアルなパズルゲームを作成しています。基本的なゲームプレイは無料ですが、プレイヤーはボーナスライフ、ヒントパック、コスメティックアイテムなどを購入して楽しむことができます。SDK IAPを通じて購入が行われます。定期購入型でないIAPアイテムには、消費型アイテム(エクストラライフなど、1回のみ使用するもの)や非消費型アイテム(新しいキャラクタースキンなど、恒久的にアンロックするもの)があります。

この方法が適している理由

非ストリーミングアプリ、特にゲームでは、短時間の頻繁なセッションが多くなる傾向があり、プレイヤーはアクティブなタスクに没頭するため、インスタントリワードやプログレッションブーストを獲得するための購入意欲が高まります。コア機能を有料にしなくても、消費型アイテムと非消費型アイテムを使用すれば、こうしたすきま時間を収益につなげることができます。

主な実装手順

定期購入型IAPアイテムの設定と同じように、開発者コンソールで [消費型アイテム] または [非消費型アイテム] を選択してIAPアイテムを追加します。

Monetizing streaming. In-app item screenshot

アイテムごとに、タイトルとSKUを指定します。例えば次のようになります。

Monetizing streaming. create new consumable screenshot

定期購入型IAPアイテムと同様、消費型アイテムおよび非消費型アイテムのIAPの実装では、PurchasingListenerを実装および登録して、PurchasingServiceに対する呼び出しからのレスポンスを処理します。

 

ただし、非消費型アイテムは、アイテムがユーザーに「配信済み」になるとPurchasingService.notifyFullfillment()が呼び出されるという点で、定期購入型アイテムとは異なります。これにより、アイテムが提供されたことがAmazonに通知され、ユーザーが同じ消費型アイテムのSKUを再購入できるようになります。また、リスナーによってonPurchaseUpdatesResponseで見つかった未付与の消費型アイテムの付与を試行して処理を行い、アイテム付与が完了する前にアプリが閉じられてもアイテムが許可されるようにします。

 

たとえば、PurchasingListenerの実装には、アイテム付与のメソッドを組み込む必要があります。

Copied to clipboard
private fun fulfillConsumable(receipt: Receipt) {
  val sku: String = receipt.sku
  val receiptId: String = receipt.receiptId

  Log.i(TAG, "SKU$skuの付与をレシートID$receiptIdで試行しています")
  // 概念的には、ここでユーザーにアイテムを提供します。
  // たとえば、ヒントやライフなどの数を増やします。
 
  // アイテムの付与が完了したことをAmazonに通知します。
  PurchasingService
    .notifyFulfillment(receiptId, FulfillmentResult.FULFILLED)
  Log.i(TAG, "FULFILLEDとマークされたレシートID$receiptIdのnotifyFulfillmentを呼び出しました")
}

このfulfillConsumable関数は、レシートが検証されたら、onPurchaseResponseハンドラーによって呼び出されます。

Copied to clipboard
override fun onPurchaseResponse(response: PurchaseResponse) {
  val skuFromReceipt: String = response.receipt?.sku ?: "N/A"

  when (response.requestStatus) {
    PurchaseResponse.RequestStatus.SUCCESSFUL -> {
      response.receipt?.let { currentReceipt ->
        val successMsg: String = "SKUの購入ステータスはSUCCESSFULです:${currentReceipt.sku}。ReceiptId:${currentReceipt.receiptId}"
        if (currentReceipt.productType == ProductType.CONSUMABLE) {
          fulfillConsumable(currentReceipt)
        } else {
          // 別のタイプのIAPを処理します。
        }
      } ?: run {
        val errorMsg: String = "購入ステータスはSUCCESSFULですが、レシートがnullです。このような状況は改善が必要です。"
        Log.e(TAG, errorMsg)
      }
    }
    PurchaseResponse.RequestStatus.FAILED -> {
      val failedMsg: String = "SKUの購入ステータスはFAILEDです:$skuFromReceipt。ログを確認してください。"
      Log.e(TAG, failedMsg)
    }
    PurchaseResponse.RequestStatus.ALREADY_PURCHASED -> {
      val alreadyPurchasedMsg: String = "SKUの購入ステータスはALREADY_PURCHASEDです:$skuFromReceipt。"
      Log.w(TAG, alreadyPurchasedMsg)
      response.receipt?.let { currentReceipt ->
        if (currentReceipt.productType == ProductType.CONSUMABLE) {
          fulfillConsumable(currentReceipt)
        }
      }
    }
    PurchaseResponse.RequestStatus.INVALID_SKU -> {
      val invalidSkuMsg: String = "購入ステータスはINVALID_SKUです:$skuFromReceipt。開発者コンソールでSKUを確認してください。"
      Log.w(TAG, invalidSkuMsg)
    }
    PurchaseResponse.RequestStatus.NOT_SUPPORTED -> {
      val notSupportedMsg: String = "購入ステータスはNOT_SUPPORTEDです。デバイスの機能またはIAPの設定を確認してください。"
      Log.w(TAG, notSupportedMsg)
    }
    PurchaseResponse.RequestStatus.PENDING -> {
      val pendingMsg: String = "SKUの購入ステータスはPENDINGです:$skuFromReceipt.解決(親の承認など)を待っています。"
      Log.i(TAG, pendingMsg)
      // 通常、ここではアプリはアクションを実行しません。Amazon
      // アプリストアが保留中の状態を処理し
      // 新しいonPurchaseResponseまたはonPurchaseUpdatesResponseが 
      // 解決時に送信されます。
    }
    null -> {
      val nullMsg: String = "PurchaseResponseのステータスはNULL(または未処理)です。このような状況は改善が必要です。"
      Log.e(TAG, nullMsg)
    }
  }
}

非ストリーミングアプリの収益化において、IAPでの消費型アイテムまたは非消費型アイテムを提供することが、柔軟で実践しやすい方法です。ユーザーの自然な行動パターンに適合し、基本機能は制限なく提供しながら、ゲームやアプリ内での特典やカスタマイズ機能を選択的に購入できる仕組みを実現できます。

 

シナリオ3: サポート対象SDKを使用した非ストリーミングアプリのアプリ内広告

仕事効率化アプリ(タスク管理アプリやガイド付きジャーナルアプリなど)を開発しています。アプリの使用は無料とし、リワード動画広告で収益化したいと考えています。たとえば、ユーザーが広告を見ることで、プレミアムテンプレートのアンロックや1日の使用制限を解除できるようにします。そのためには、サポートされているサードパーティ製広告SDK(ChartboostやAdColonyなど)を統合します。

この方法が適している理由

非ストリーミングアプリは、短時間の頻繁なセッションが多いことから、長編コンテンツを中断しないリワード広告やインタースティシャル広告に最適です。リワード動画広告は、ユーザーにとっては視聴することでリワードが獲得でき、開発者にとっては有料化することなく収益を得ることができます。このモデルは、一般的に繰り返し利用されるユーティリティアプリやカジュアルアプリの類に特に効果的です。

主な実装手順

Amazonがサポートする広告SDKの選択から始めます。次の例では、Chartboost Android SDK(英語のみ)を使用します。

 

Chartboost SDKをインポートする新しいActivity(例:RewardedAdsActivity.kt)をアプリに実装します。

Copied to clipboard
import com.chartboost.sdk.Chartboost
import com.chartboost.sdk.ads.Rewarded
import com.chartboost.sdk.LoggingLevel
import com.chartboost.sdk.events.*
import com.chartboost.sdk.callbacks.RewardedCallback
import com.chartboost.sdk.privacy.model.DataUseConsent

RewardedAdsActivityonCreate関数内で、SDKを初期化します。

Copied to clipboard
// 実際のChartboostアプリIDとアプリ署名に置き換えます。
private val chartboostAppID: String = "CHARTBOOST_APP_ID"
private val chartboostAppSignature: String = "CHARTBOOST_APP_SIGNATURE"

// リワード広告のデフォルトの位置を定義します。
const val REWARDED_AD_LOCATION = "default-rewarded_video"

…

override fun onCreate(savedInstanceState: Bundle?) {
  …

  // SDKを初期化する前にデータの使用許諾を設定することが重要です。
  // 実際のアプリでは、これはユーザーの同意に基づきます。
  // この例では、プレースホルダーを使用します。
  Chartboost.addDataUseConsent(this, DataUseConsent.GDPR("1"))
  Chartboost.addDataUseConsent(this, DataUseConsent.CCPA("1---"))

  // 詳細ログを有効にします。startWithAppIdの前にこれを呼び出します。
  Chartboost.setLoggingLevel(LoggingLevel.ALL)

  Chartboost.startWithAppId(
    applicationContext, chartboostAppID, chartboostAppSignature
  ) { startError ->
        if (startError == null) {
          val message = "Chartboost SDKが正常に初期化されました。"
          Log.d("ChartboostInit", message)
          // 初期化が正常に完了したら、広告をプリキャッシュします。
          loadRewardedAd()
        } else {
          val errorMessage = "Chartboost SDKの初期化に失敗しました:${startError.code.name}"
          Log.e("ChartboostInit", errorMessage)
        }
   }
}
…

リワード広告をキャッシュするには、広告を表示するために広告の読み込みとキャッシングを実装する必要があります。RewardedAdsActivity内で定義された以下の関数は、その方法を示しています。

Copied to clipboard
private fun loadRewardedAd() {
  if (rewardedAd == null || !rewardedAd!!.isCached()) {
    createAndCacheRewardedAd()
  } else {
    Log.d("ChartboostRewarded", "$REWARDED_AD_LOCATIONのリワード広告は既にキャッシュされています。")
    showAdButton.isEnabled = true
  }
}

private fun createAndCacheRewardedAd() {
  rewardedAd = Rewarded(REWARDED_AD_LOCATION, object : RewardedCallback {
  
    override fun onAdLoaded(event: CacheEvent, error: CacheError?) {
      if (error != null) {
        val errorMessage = "位置${event.ad.location}のリワード広告の読み込みに失敗しました:${error.code.name}"
        Log.e("ChartboostRewarded", errorMessage)
        showAdButton.isEnabled = false
      } else {
        Log.d("ChartboostRewarded", "位置${event.ad.location}のリワード広告の読み込みが正常に完了しました。")
        showAdButton.isEnabled = true
      }
    }

    override fun onAdRequestedToShow(event: ShowEvent) {
      Log.d("ChartboostRewarded", "${event.ad.location}の位置のリワード広告の表示が要求されました。")
    }

    override fun onAdShown(event: ShowEvent, error: ShowError?) {
      if (error != null) {
        Log.e("ChartboostRewarded", "位置${event.ad.location}のリワード広告の表示に失敗しました:${error.code.name}")
      } else {
        Log.d("ChartboostRewarded", "位置${event.ad.location}リワード広告が正常に表示されました。")
      }
    }

    override fun onAdClicked(event: ClickEvent, error: ClickError?) {
      val message = if (error == null) {
        "位置${event.ad.location}のリワード広告がクリックされました。"
      } else {
        "位置${event.ad.location}のリワード広告のクリックエラー:${error.code.name}"
      }
      Log.d("ChartboostRewarded", message)
    }

    override fun onAdDismiss(event: DismissEvent) {
      Log.d("ChartboostRewarded", "位置${event.ad.location}のリワード広告が閉じられました。")
      showAdButton.isEnabled = false
      
      // 広告が閉じられた後で次の広告をキャッシュすることをお勧めします。
      loadRewardedAd()
    }

    override fun onRewardEarned(event: RewardEvent) {
      Log.d("ChartboostRewarded", "ユーザーが位置${event.ad.location}の広告を見てリワード${event.reward}を獲得しました。")

      // ここでユーザーにリワード (例:コイン10個)を付与します。
      // val currentCoins = sharedPreferences.getInt("user_coins", 0)
      // sharedPreferences.edit().putInt("user_coins", currentCoins + event.reward).apply()
      // updateUiWithNewCoinBalance(currentCoins + event.reward)
      // Log.d("ChartboostRewarded", "ユーザーに${event.reward}のコインが付与されました。現在のコイン残高:${currentCoins + event.reward}")
     

    }

    override fun onImpressionRecorded(event: ImpressionEvent) {
      Log.d("ChartboostRewarded", "位置${event.ad.location}のリワード広告のインプレッションが記録されました。")
    }
  }, null)

  rewardedAd?.cache()
}

private fun showRewardedAd() {
  if (rewardedAd != null && rewardedAd!!.isCached()) {
    rewardedAd?.show()
  } else {
    Log.w("ChartboostRewarded", "$REWARDED_AD_LOCATIONに広告を表示しようとしましたが、キャッシュされていません。")
    showAdButton.isEnabled = false
    
    // 表示されない場合、読み込みを試みます。
    loadRewardedAd()
  }
}

上記の実装の中心となるのは、Rewarded(英語のみ)広告オブジェクトの作成です。これはユーザーにリワードを提供する全画面広告です。Rewardedオブジェクトの作成では、RewardedCallback(英語のみ)でさまざまな広告ライフサイクルイベントを処理します。

 

広告コールバック

  • onAdLoaded: 広告が正常にキャッシュされて表示が可能になるとトリガーされます。ここでUIを更新([広告を表示] ボタンの有効化など)しますが、失敗した読み込みの処理も行います。
  • onAdShown: 広告がユーザーに表示されるとトリガーされます。
  • onAdClicked: ユーザーが広告をクリックするとトリガーされます。
  • onImpressionRecorded: 広告がインプレッションを記録するとトリガーされます。

削除可能な広告コールバック

  • onAdDismiss: ユーザーが広告を閉じるとトリガーされます。この時点で、次のリワード広告を読み込んで表示できるようにします。

リワードコールバック

  • onRewardEarned: ユーザーが動画の視聴を正常に完了するとトリガーされます。これは不可欠なコールバックです。RewardEventはリワードの数量を設定します。ここで実際のリワードをユーザーに付与します(例:通貨の更新、アイテムのアンロック)。

 

リワード広告は、タスクベースのインタラクションが繰り返される非ストリーミングアプリで実践しやすい収益化方法です。サポートされているSDKを統合して広告をユーザーのメリットにつなげることで、顧客満足度を損ねることなく収益化できます。

シナリオ4: サポート対象広告SDKを使用したストリーミングアプリのアプリ内広告

インディーズ映画やミュージックビデオなどの厳選されたコンテンツを提供するニッチな動画ストリーミングアプリを開発しています。コンテンツは無料のまま、アプリ内広告で収益を得ることが目標です。これを達成するには、通常、ストリーミングアプリでプリロール広告やインタースティシャル広告を配信できるサポート対象SDKを実装します。

 

コンテンツやトラフィックの量が多いエンタープライズクラスのアプリ向けに、AmazonはAmazon Publisher Services(APS)(英語のみ)を提供しています。これは、Amazonとサードパーティ広告主からのリアルタイム入札を可能にするプログラマティック広告ソリューションです。ただし、APSを利用できるアプリプロセスは限られています。ほとんどのストリーミングアプリでは、互換性のある動画広告SDKの実装が、依然として最も実用的かつ効果的な収益化の方法です。

この方法が適している理由

ストリーミングアプリは、広告ベースの収益化に最適です。ユーザーが無料コンテンツを求めている場合や、セッション時間が複数の広告を掲載できるほど長い場合はなおさらです。ストリーミング環境では、動画コンテンツの切れ目や再生開始前に広告を配置することで、効果的な広告表示が可能です。特にFire TVでは全画面で動画広告を表示できるため、より高い広告効果が期待できます。

主な実装手順

前のシナリオと同様、まず、Amazonがサポートする広告SDKを選択します。ただし、リワード広告ではなく、インタースティシャル広告を表示します。

 

ストリーミングアプリ内で、自然なブレークポイントにインタースティシャル広告を配置します。一般的には、ユーザーが動画コンテンツの再生をリクエストすると、プリロールにインタースティシャル広告を配置します。インタースティシャル広告が最後まで視聴されたら、アプリが動画コンテンツをユーザーに表示します。

 

Chartboostでのインタースティシャル広告の実装は、リワード広告の場合とよく似ています。前のシナリオと同じように、以下の手順から始めます。

 

  • Chartboost SDKをインポートします。
  • addDataUseConsentを呼び出して、ユーザーの同意があるかないか、または不明かを指定します。
  • SDKのログレベルを適切に設定します。
  • アプリIDとアプリ署名でstartWithAppIdを呼び出し、SDKを初期化します。

 

続いて、InterstitialCallback(英語のみ)内にメソッドを実装するInterstitial(英語のみ)オブジェクトを作成します。

Copied to clipboard
nterstitialAd = Interstitial("location", object : InterstitialCallback {

  override fun onAdDismiss(dismissEvent: DismissEvent) {
    // 広告が閉じられると呼び出されます。
    // リクエストされた動画を再生せずに、動画ギャラリーにユーザーを戻します。
  }

  override fun onAdLoaded(cacheEvent: CacheEvent, cacheError: CacheError) {
    // 広告がChartboostサーバーから読み込まれてキャッシュされました。
    // CacheErrorがない場合、広告は表示できる状態です。
  }

  override fun onAdRequestedToShow(showEvent: ShowEvent) {
    // 広告が表示される直前に呼び出されます。
  }

  override fun onAdShown(showEvent: ShowEvent, showError: ShowError?) {
    // 広告が表示されるかインプレッションが記録されたら呼び出されます。
    // ユーザーからリクエストされた動画の再生に進みます。
  }

  override fun onAdClicked(clickEvent: ClickEvent, clickError: ClickError?)
  {
    // 広告がクリックされたら呼び出されます。
    // 発生時にキャプチャ(記録、通知)したい場合に使用します。
  }

  override fun onImpressionRecorded(impressionEvent: ImpressionEvent) {
    // 広告がインプレッションを記録したら呼び出されます。
    // 発生時にキャプチャ(記録、通知)したい場合に使用します。
  }

}, null)

インタースティシャルをキャッシュするには、次のメソッドを呼び出します。

Copied to clipboard
interstitialAd.cache()

次に、広告を表示できる状態になったら(ユーザーが動画の視聴をリクエストしてから動画のストリーミングが開始するまでの間)、以下を呼び出します。

Copied to clipboard
if (interstitialAd.isCached()) {
  interstitialAd.show()
}

ストリーミングアプリでの広告の収益化は、特に広告の関連性が高く、配置が適切で、邪魔にならない場合は、高い効果が得られる可能性があります。サポート対象SDKとAPSのどちらを使用する場合でも、熟考して統合することで、収益化とユーザーエクスペリエンスを両立できます。

収益化のモニタリングと最適化

収益化戦略を実装したら、そのパフォーマンスを把握し、改善点を見つけましょう。その第一歩として役に立つのが、Amazonの収益化レポートです。このレポートのダッシュボードでは、収益の傾向、アクティブな定期購入、リテンション、解約率などを確認できます。まだご覧になったことがない方は、収益化レポートの概要を紹介するこちらの記事をぜひ参照してください。

 

収益化レポートは、多種多様なアプリの統計データレポートの1つにすぎません。日々インサイトを得るために、これらのレポートでアプリのユーザーエンゲージメントに関する詳細なデータを確認しましょう。レポートは以下の3つに大きく分類されます。

ユーザー獲得(Fire TVのみ)

Fire TVでのアプリの検索やインストールの傾向を把握するのに役立ちます。ユーザー獲得レポートには、カテゴリーページ、検索結果、「おすすめ」などのAmazon選出枠をはじめとする上位トラフィックソースが示されます。

 

最もインストールされているキーワード、アプリが検索結果に表示される頻度、クリックやインストールにつながるインプレッション率を確認し、このデータを活用することで、アプリストアのリスティングやタイトル、メタデータを最適化し、注目度やコンバージョンを高めることができます。

Reporting dashboard screenshot

エンゲージメント

インストール後にアプリがどのように使用されているかを追跡します。エンゲージメントレポートには、1日および1か月のアクティブユーザー数、合計セッション数、セッションの長さの平均、粘着率(DAU/MAU比率)が示されます。これらのインサイトは、使用パターンの把握やリテンション率の高いセグメントの特定に役立ちます。また、エンゲージメントをアプリのバージョン別に分析して、機能の変更やバグの修正がユーザーアクティビティに及ぼす経時的な影響を測定することもできます。

Reporting dashboard screenshot

収益化

定期購入型IAPアイテムの収益化の傾向をつかむには、収益化レポートを確認します。定期購入 - 概要レポート、リテンションレポート、キャンセルレポートで構成されるこのレポートは、定期購入ユーザーの行動(定期購入を開始、継続、またはキャンセルしたユーザーの数など)の追跡のほか、平均期間やキャンセル理由の把握に役立ちます。

Reporting dashboard screenshot

 

1回限りの購入(消費型アイテム、非消費型アイテム)や広告ベースのモデルに関しては、別のアプリの統計データレポートでIAPイベントの集計データを確認します。広告収益に限っては、広告SDKプロバイダーのダッシュボードを使用して、インプレッション、フィルレート、eCPMなどの主要指標を追跡してください。これらのツールを使用すると、収益化戦略の改善策や、データに基づいたアップデートによってユーザーの行動に対応する方法が見えてきます。

 

アプリの統計データレポートは、アプリの収益化に関する効果的なレポートと分析を提供します。それらを使用して、収益化のタイミングを見極め、フィーチャー枠をテストし、次に何を最適化すべきかを十分な情報に基づいて判断しましょう。

最後に

ストリーミングアプリとタスクベースのユーティリティのどちらを開発する場合でも、ユーザーがコンテンツを実際にどう使用するかに応じて収益化戦略を立てる必要があります。定期購入型アイテム、消費型アイテム、リワード広告、プログラマティック動画には、それぞれの特性に応じた活用方法があります。重要なのは、適切なコンテンツに適切なツールを選ぶことです。

 

SDK、API、開発者ツールは、あらゆるアプリタイプやデバイスでの収益化に柔軟性をもたらします。適切に実装を行い、パフォーマンスデータに目を光らせれば、大きな収益を生みながら、ユーザーに価値を提供することができます。

関連記事

ニュースレターを購読してみませんか?

最新のAmazon開発者向けニュース、業界の動向、ブログの記事をお届けします。