---
title: "Ionic、Angular、RxJS、and NgRxでオーディオ プレーヤーアプリを構築する"
description: "Angular、Ionic、RxJS、NgRx で最新モバイルアプリを開発する方法を学びましょう。このチュートリアルに従ってモバイルオーディオプレーヤーアプリを作りましょう。"
authors:
  - name: "Indermohan Singh"
    url: "https://auth0.com/blog/authors/indermohan-singh/"
date: "Jul 12, 2018"
category: "Developers,Tutorial,Angular"
tags: ["ionic", "angular", "rxjs", "ngrx", "mobile", "auth0", "hybrid"]
url: "https://auth0.com/blog/jp-building-an-audio-player-app-with-ionic-angular-rxjs-and-ngrx/"
---

# Ionic、Angular、RxJS、and NgRxでオーディオ プレーヤーアプリを構築する



**TL;DR**：本書では、[**Ionic framework**](https://ionicframework.com/)、[**Angular**](https://angular.io/) を使ってモバイルオーディオプレーヤーアプリを作成する方法について学んでいきます。[*RxJS*](https://github.com/ReactiveX/rxjs) や *Observable* を使うオーディオ操作を処理し、 [**NgRx**](https://github.com/ngrx/platform) でアプリケーションの状態を管理する方法についても学んでいきます。 アプリケーションをセキュアにするには、[**Auth0**](https://auth0.com/) を使用します。必要であれば、[_この__ GitHub __レポジトリで最後のコードを見つけることもできます_](https://github.com/auth0-blog/ionic-audio-player)。

## はじめに

オーディオプレーヤーを作ることは威圧を感じる作業です。メディアの状態 管理、メディア イベントへの応答、そしてこれら変更を UI（ユーザー インターフェイス）に適切に反映させるなどを考えると特にです。ですから、本書では、これら問題を簡単に解決する Angular と Ionic を（他のライブラリも一緒に）使います。

メディア再生を反応の早い方法で処理するには、[_JavaScript __の__ _`Audio`_ __オブジェクト_](https://www.w3schools.com/jsref/dom_obj_audio.asp)を **RxJS Observable** に適用し、**NgRx** ストアを使ってオーディオプレーヤーの状態を管理します。

そのほかに、モバイルアプリを安全にする [_Auth0_](https://auth0.com/) を使用しますが、後のアーティクルでは、音楽ファイルのリストをアプリに提供するために安全なバックエンドを作る方法について学びます（本書では、スタティック データのモックサービスを使用します）。

<include src="JpTweetQuote" quoteText="@Ionicframework、@angular、RxJS、NgRx を使ってモバイルオーディオプレーヤーアプリを作りましょう。"/>

## Ionic 開発の必要条件

これからモバイルアプリケーションを作るので、アプリを作るのに必要な SDK が必要です。本書では、Ionic アプリをネイティブ モバイルパッケージにする [_Cordova_](https://cordova.apache.org/) を使用します。

次のセクションでは、アプリケーションを作成する前に従わなければならないステップについて説明します。

### iOS 用ネイティブ SDK をインストールする

iOS プラットフォームには Mac OS X 環境が必要で、[_Xcode_](https://developer.apple.com/xcode/) がインストールされていなければなりません。[_ネイティブ__ SDK __を__ Mac OS X __環境に構成することについては、このリファレンスを確認_](https://cordova.apache.org/docs/en/latest/guide/platforms/ios/)してください。Xcode をインストールしたら、コマンドライン ツールとシミュレーターを実行する `ios-deploy` ツールも必要です。

これらツールをインストールするには、次に従ってください。

* コマンドラインから `xcode-select --install` を実行して、Xcode コマンドラインツールをインストールします。
* それから、`npm install -g ios-deploy` を実行して `ios-deploy` ツールをインストールします。

[_マシンに__ Node.js __と__ NPM __がインストールされていない場合は、このレファレンスを確認_](https://nodejs.org/en/download/)してください。

### Android 用ネイティブ SDK をインストールする

Android アプリケーションには、Android SDK とツールが必要です。以下のステップでは、ご使用の環境に SDK とツールをインストールする方法を説明していますが、詳細説明が必要な場合は、[_すべてをインストールする方法について詳しく説明するこのリンクを確認してください_](https://cordova.apache.org/docs/en/latest/guide/platforms/android/index.html)。

* JDK：[_JDK __をインストール_](http://www.oracle.com/technetwork/java/javase/downloads/index.html)し、 JDK インストールを指す `JAVA_HOME` 環境変数をインストールする必要があります。
* Gradle：[_Gradle_](https://gradle.org/) もインストールし、それを環境変数の `PATH` 変数に追加する必要があります。
* Android SDK：最も重要なのは、アプリに `apk` ファイルを生成する Android SDK が必要ですから、[_Android Studio IDE_](https://developer.android.com/studio/) をインストールし、`sdkmanager` を使って次をインストールします。
    1. Android Platform SDK
    2. SDK バージョン用ビルド ツール
    3. Android サポートレポジトリ

これらをインストールしたら、`ANDROID_HOME` 環境変数を Android SDK の位置情報に設定する必要があります。Android SDK の `tools` ディレクトリ、`tools/bin` ディレクトリ、`platform-tools` ディレクトリを `PATH` 変数に追加することもお勧めします。

### Node.js とツールをインストールする

すでにお伝えしましたが、開発マシンに Node.js をインストールする必要がありますので、まだインストールされていないのであれば、 [_Node.js __のダウンロードページ_](https://nodejs.org/en/download/)に行き、その指示に従ってください。

インストールが終わったら、`npm` を介して Cordova CLI と Ionic CLI をインストールする必要があります。

```bash
npm install -g ionic cordova
```

## Ionic アプリをスキャフォールディングする

すべての環境依存関係をインストールしたら、Ionic アプリのスキャフォールディングに集中して実行します。このためには、次のコマンドを端末に発行します。

```bash
ionic start audio-player blank
```

このコマンドで次の2つの質問を表示されます。

1. _ネイティブ iOS と Android をターゲットにする Cordova を新しいアプリに統合しますか？（はい/いいえ) :_ モバイルデバイスのアプリを作成するのであれば、`y`（はい）を入力します。
2. _無料の Ionic Pro SDK をインストールし、お使いのアプリに接続しますか？（はい/いいえ）_：Ionic Pro 機能をこのチュートリアルで使用する必要がない場合は、`n` を押してください。

### アプリケーションを実行する

次に進む前に、モバイルデバイスやエミュレーターでアプリケーションを始めることができるかを確認します。

たとえば、Mac OS X 環境をご使用で、アプリケーションをテストするためにエミュレーターを使用する場合は、次を実行します。

```bash
# iOS アプリの場合
ionic cordova run ios -lc
```

> **注**：上記の `-lc` は Ionic が サーバーをスピンアップして www ファイルをライブでリロードする*（`l`）ことと、コンソールログを端末に印刷する*（`c`）ことを意味します。

参考として、以下は現在の開発マシン（ブラウザーがあるものなど）や Android を狙うときに使用できる他のコマンドです。

```bash
# ローカルで使用可能にします
ionic serve

# android アプリの場合
ionic cordova run android
```

### プロジェクトの依存関係をインストールする

基本的なアプリをモバイルデバイスで実行できることが確認できたら、依存関係をインストールして次のステップに進みます。モバイルオーディオプレーヤーを作るには、次の NPM ライブラリを使います。

* [`@angular/animations`](https://www.npmjs.com/package/@angular/animations)：アニメーションを追加してアプリ UX を向上するパッケージ
* [`@ngrx/store`](https://github.com/ngrx/store)：アプリケーションの状況を管理するのに役立つように RxJS アプリケーションと Angular アプリケーションを統合して構築するライブラリ
* [`moment.js`](https://momentjs.com/)：日付や時間を JavaScript で操作するのに役立つライブラリ
* [`auth0-js`](https://www.npmjs.com/package/auth0-js)：JavaScript アプリ用の公式 Auth0 ライブラリ
* [`@auth0/cordova`](https://github.com/auth0/auth0-cordova)：Cordova アプリ用の公式 Auth0 ライブラリ
* [`rxjs`](https://github.com/ReactiveX/rxjs)：JavaScript 用のリアクティブプログラミングライブラリ
* [`rxjs-compat`](https://github.com/ReactiveX/rxjs/tree/master/compat)：RxJS previous との互換性を下位のバージョン６にするパッケージ

上記のライブラリをインストールするには、次のコマンドを使用します。

```bash
# プロジェクトルートにいることを確認します
cd audio-player

# すべてのライブラリをインストールします
npm install --save @angular/animations @ngrx/store moment auth0-js @types/auth0-js @auth0/cordova rxjs@6.2.1 rxjs-compat@6.2.1
```

> **注**：上記のコマンドでは、Ionic （少なくても本書の作成時）は Angular 5 を同梱し、Angular 5 は RxJS 5 API を使用するので、`rxjs@6.2.1` と `rxjs-compat@6.2.1` をインストールしました。

## 再生を管理する Ionic サービスを作成する

アプリの依存関係をインストールしたら、再生機能に取り組みます。

### RxJS Observable を作成する

これから作成する Observable はアプリケーションの中心的なものです。RxJS にはカスタム Observable を作るのに役立つ `create` という名前のヘルパー機能が同梱されています。入力として `subscribe` 関数が必要です。

```typescript
Observable.create(subscribe): Observable<any>;
```

この `subscribe` 関数は `observer` オブジェクトを取り、関数を返します。Observer オブジェクトは `Next`、`error`、`complete` の３つのメソッドを提供します。

1. 値を発行するには、必要な値で `observer.next` メソッドを呼び出します。
2. エラーの場合、そのエラーをスローする `observer.error` 関数を使って Observable を停止させます。
3. これから Observable が必要でなく発行する値がなければ、`observer.complete` メソッドを呼び出します。

`Observable.create` も呼び出して `Observable` を `subscribe` メソッドを介してサブスクライブできるものに返します。このメソッドは Observable から解除したいときに呼び出すことができる関数を返します。

> `Observable.create(subscribe)` と `Observable.subscribe()` を間違えないようにしてください。以前の `subscribe` 関数は `Observable.create` への入力で、これは Observable の青写真のようなもので、後者は Observable の実行を呼び出すものです。

オーディオプレーヤーアプリでは、`playing`、`pause`、`timeupdate` などのようなメディアイベントについての通知を得るための Observable を作りますから、簡単に言えば、Observable 内の `Audio()` のメディアイベントに耳を傾けてから、`observer.next` メソッドを介してアプリの残りを通知します。

これで、Observable が必要であることをご理解いただけたと思いますので、サービスを Ionic アプリに作成することから始めましょう。

```bash
ionic generate provider audio
```

これによって、`./src/providers/audio/` の下の `audio.ts` と呼ばれるファイルにサービスを生成し、このサービスは `app.module.ts` の`NgModule` に追加されます。`audio.ts` ファイルのコンテンツを次と置き換えます。

```typescript
import {Injectable} from '@angular/core';
import {Observable, Subject} from 'rxjs';
import {takeUntil} from 'rxjs/operators';
import * as moment from 'moment';

@Injectable()
export class AudioProvider {
  private stop$ = new Subject();
  private audioObj = new Audio();

  constructor() { }

  private streamObservable(url) {
    let events = [
      'ended', 'error', 'play', 'playing', 'pause', 'timeupdate', 'canplay', 'loadedmetadata', 'loadstart'
    ];

    const addEvents = function(obj, events, handler) {
      events.forEach(event => {
        obj.addEventListener(event, handler);
      });
    };

    const removeEvents = function(obj, events, handler) {
      events.forEach(event => {
        obj.removeEventListener(event, handler);
      });
    };

    return Observable.create(observer => {
      // 再生オーディオ
      this.audioObj.src = url;
      this.audioObj.load();
      this.audioObj.play();

      // メディアイベント
      const handler = (event) => observer.next(event);
      addEvents(this.audioObj, events, handler);

      return () => {
        // 再生停止
        this.audioObj.pause();
        this.audioObj.currentTime = 0;

        // EventListeners を削除します
        removeEvents(this.audioObj, events, handler);
      };
    });
  }
}
```

これで、新しいオーディオファイルを再生したいときは、この Observable を作成してこれらすべてのメディアイベントを聞きます。これは `AudioProvider` クラスに追加する `playStream()` と呼ばれる新しいメソッドを介して行います。

```typescript
// ... ステートメントをインポートします ...

export class AudioProvider {
  // ... コンストラクターとその他メソッド ...

  playStream(url) {
    return this.streamObservable(url).pipe(takeUntil(this.stop$));
  }
}
```

`this.stop$` が任意の値を発行したら、この Observable から自動的に解除されることにご留意いただくことが大切です。

### AudioProvider サービスを完成させる

これで `AudioProvider` サービスの基盤ができたので、 メソッドの残りの `play`、`pause`、`stop`、`seekTo`、`formatTime` を実行します。これらの実装は分かりやすいので、これら５つのメソッドを以下に表示のように `AudioProvider` サービスに追加します。

```typescript
// ... ステートメントをインポートします ...

export class AudioProvider {

  // ... コンストラクターとその他メソッド ...

  play() {
    this.audioObj.play();
  }

  pause() {
    this.audioObj.pause();
  }

  stop() {
    this.stop$.next();
  }

  seekTo(seconds) {
    this.audioObj.currentTime = seconds;
  }

  formatTime(time, format) {
    return moment.utc(time).format(format);
  }
}
```

## 音楽ファイルを読み取る

再生機能のオーディオサービスを作った後、ファイルのリストを取得するサービスを作る必要があります。このために、Ionic を使ってクラウドサービスを作ります。

```bash
ionic generate provider cloud
```

このコマンドは `./src/providers/cloud` の下の `cloud.ts` という名前のファイルにサービスを生成します。ここで、このファイルのコンテンツを次と置き換えます。

```typescript
import { Injectable } from '@angular/core';
import { of } from 'rxjs';

@Injectable()
export class CloudProvider {
  files:any = [
    { url: 'https://ia801504.us.archive.org/3/items/EdSheeranPerfectOfficialMusicVideoListenVid.com/Ed_Sheeran_-_Perfect_Official_Music_Video%5BListenVid.com%5D.mp3',
      name: 'Perfect by Ed Sheeran'
    },
    {
      url: 'https://ia801609.us.archive.org/16/items/nusratcollection_20170414_0953/Man%20Atkiya%20Beparwah%20De%20Naal%20Nusrat%20Fateh%20Ali%20Khan.mp3',
      name: 'Man Atkeya Beparwah by Nusrat Fateh Ali Khan'
    },
    { url: 'https://ia801503.us.archive.org/15/items/TheBeatlesPennyLane_201805/The%20Beatles%20-%20Penny%20Lane.mp3',
      name: 'Penny Lane by The Beatles'
    }
  ];
  getFiles() {
   return of(this.files);
  }
}
```

上記の `getFiles` メソッドが基本的に、スタティック `files` オブジェクトで `Observable` を返して HTTP リクエストをモックします。

## Ionic アプリの状態を NgRx ストアで管理する

アプリケーションの状態を管理するのに役立てるため [_NgRx __ストアライブラリ_](https://github.com/ngrx/store)を活用します。このストアは Redux をベースにしたもので、React の世界では状態を管理するのに非常に有名なもので、Redux 概念と RxJS とを統合します。

Redux が何か（またはその機能）をご存知でない方のために、次で簡単に説明しています。

> Redux では、状態は中心的位置で管理されています。つまり、アプリケーション全体の最新状態を格納するオブジェクトがひとつだけということです。どの時点であっても、この状態を更新したいのであれば、 `reducer` として知られる関数に `action` を `dispatch` する必要があります。この `reducer` は `action` を理解し、`type` と `data` のアクションをベースにした新しい状態を生成する責任があります。

### NgRx ストアで Reducer を作成する

デフォルトで、NgRx `Action` インターフェースはひとつのプロパティ `type` だけを表示します。アクションのタイプと一緒に情報を送信する必要があるので、ニーズに合わせて NgRx `Action` インターフェイスを拡張します。

ですから、インターフェイスの拡張を定義するには、`store`（`./src/providers/` の下）という名前の新しいディレクトリ内の `store.ts` という名前のファイルを作り、次のコードをそれに追加します。

```typescript
import {Action} from '@ngrx/store';

export interface MediaAction extends Action {
  type: string;
  payload?: any;
}
```

それから、異なるメディアイベントに異なるアクションを作ります（`canplay`、`playing`、など）。そのようなものとして、`store.ts` ファイルを次のように更新します。

```typescript
// ... ステートメントと MediaAction インターフェイスをインポートします ...

export const CANPLAY = 'CANPLAY';
export const LOADEDMETADATA = 'LOADEDMETADATA';
export const PLAYING = 'PLAYING';
export const TIMEUPDATE = 'TIMEUPDATE';
export const LOADSTART = 'LOADSTART';
export const RESET = 'RESET';
```

その後、`MediaAction` のインスタンスを受信し、処理する Reducer 関数を実装できるようになります。

```typescript
// ... インポート、MediaAction、Const ...

export function mediaStateReducer(state: any, action: MediaAction) {
  let payload = action.payload;
  switch (action.type) {
    case CANPLAY:
      state = Object.assign({}, state);
      state.media.canplay = payload.value;
      return state;
    case LOADEDMETADATA:
      state = Object.assign({}, state);
      state.media.loadedmetadata = payload.value;
      state.media.duration = payload.data.time;
      state.media.durationSec = payload.data.timeSec;
      state.media.mediaType = payload.data.mediaType;
      return state;
    case PLAYING:
      state = Object.assign({}, state);
      state.media.playing = payload.value;
      return state;
    case TIMEUPDATE:
      state = Object.assign({}, state);
      state.media.time = payload.time;
      state.media.timeSec = payload.timeSec;
      return state;
    case LOADSTART:
      state.media.loadstart = payload.value;
      return Object.assign({}, state);
    case RESET:
      state = Object.assign({}, state);
      state.media = {};
      return state;
    default:
      state = {};
      state.media = {};
      return state;
  }
}
```

上記のコード内の各 `case` ステートメント内で、アプリに新しい `state` を生成していきます。NgRx は `immutable` オブジェクトと連携するので、既存のものを更新する代わりに新しい状態オブジェクトを作る必要があることに留意することが重要です。この場合、現在のものをベースにして新しい `state` オブジェクトを作る `Object.assign` を使用します。

ここで、Ionic アプリの Reducer を登録するには、`app.module.ts` ファイルを開き、次のように更新します。

```typescript
// ... その他のステートメントをインポートします ...
import { StoreModule } from '@ngrx/store';
import { mediaStateReducer } from '../providers/store/store';

@NgModule({
  // ... 宣言 ...
  imports: [
    // ... その他インポートされたモジュール ...
    StoreModule.forRoot({
      appState: mediaStateReducer
    }),
    IonicModule.forRoot(MyApp)
  ],
  // ... ブートストラップ、entryComponents、プロバイダー ...
})
export class AppModule {}
```

これで Ionic アプリケーションのどこにでもある `appState` キーを使って現在の状態にアクセスできるようになります。

## Ionic アプリの認証

セキュリティアプリを作成するには、ユーザーの認証を処理する Auth0 に依存します。そのようなものとして、[_無料__ Auth0 __アカウントはこちらから登録_](https://auth0.com/signup)してください。それから、モバイルアプリを表す Auth0 アプリケーションをセットアップする必要があります。

### 依存関係をインストールする

Ionic アプリを Auth0 でセキュアにするには、Cordova プラグインをインストール必要があります。

```bash
# {YOUR_PACKAGE_ID} と独自のアプリ識別子を置き換え、
# YOUR_AUTH0_DOMAIN を独自の Auth0 ドメインと置き換えます
ionic cordova plugin add cordova-plugin-customurlscheme --variable URL_SCHEME={YOUR_PACKAGE_ID} --variable ANDROID_SCHEME={YOUR_PACKAGE_ID} --variable ANDROID_HOST={YOUR_AUTH0_DOMAIN} --variable ANDROID_PATHPREFIX=/cordova/{YOUR_PACKAGE_ID}/callback
ionic cordova plugin add cordova-plugin-safariviewcontroller
```

> **注：**上記の `{YOUR_PACKAGE_ID}` と Ionic アプリのパッケージ ID を置き換えます。この情報は `config.xml` ファイルにあります。このファイルには `<widget id="io.ionic.starter" ...` のような情報があります。この場合、パッケージ ID は `io.ionic.starter` です。

> **注：**そのほかに `{YOUR_AUTH0_DOMAIN}` と独自の Auth0 ドメインを置き換える必要があります。Auth0 アカウントを作成するとき、`ionic-audio-player`、`your-name`、などのようなサブドメインを選択します。この場合、Auth0 ドメインは `ionic-audio-player.auth0.com` になります。次のスクリーンショットに表示されているように、Auth0 ダッシュボードの右上にもサブドメインがあります。

![Find your Auth0 subdomain.](https://images.ctfassets.net/23aumh6u8s0i/6f4a7ZGzhEQIY6kijvLc3x/2c9290d4e68326acf5cb69f2ceca883d/find-auth0-domain)

### Auth0 アプリケーションをセットアップする

1. [**Auth0 ****ダッシュボード**](https://manage.auth0.com/)に移動し、「[_新しいアプリケーションの作成_](https://manage.auth0.com/)」ボタンをクリックします。
2. 新しいアプリの名前を付け（「Ionic オーディオ プレーヤー」など）、そのタイプに「ネイティブアプリ」を選択し、「作成」ボタンをクリックします。
3. 新しい Auth0 アプリの**設定**タブで、**許可されたオリジン（CORS)** ボックスの `file://*, http://localhost:8080` を追加します。
4. **設定**タブで、`YOUR_PACKAGE_ID://YOUR_AUTH_DOMAIN/cordova/YOUR_PACKAGE_ID/callback` を **許可されたコールバック URL** に追加します。
5. `http://localhost:8080` を **許可されたログアウト URL** に追加します。
6. 「変更の保存」ボタンをクリックします。

**注：**アプリをライブでの再読み込み機能で実行しているのであれば、 `http://localhost:8080` とは異なる URL を **許可されたオリジン（CORS)** ボックスに追加しなければならないかもしれません。アプリを実行しているとき、正しい URL を見つけるために `config.xml` ファイルの `allow-navigation` プロパティを確認します。例えば：`http://192.168.0.14:8100` などです。

**注：**ステップ４では、`YOUR_PACKAGE_ID` と `YOUR_AUTH_DOMAIN` を独自のデータ（ `io.ionic.starter` や `ionic-audio-player.auth0.com` のようなプロジェクトの依存関係をインストールしたときに使用した同じデータ）と交換します。

### Ionic で Auth0 を構成する

ここで、`auth.config.ts` という名前のファイルを新しいディレクトリの `./src/providers/auth0/` に作成します。そのファイル内に、次のコードを追加します。

```typescript
export const AUTH_CONFIG = {
  clientID: 'YOUR_CLIENT_ID',// Auth0 に必要（大文字化：ID）
  clientId: 'YOUR_CLIENT_ID', // Auth0Cordova に必要（大文字化：Id）
  domain: 'YOUR_AUTH_DOMAIN',
  packageIdentifier: 'your.app.id' // config.xml ウィジェット ID にあります（com.auth0.ionic など）
};
```

以下のリストがこれら値の意味を説明します。

* `clientID` と `clientId`：Auth0 アプリケーション（上記で作成したもの）で使用可能な *Client Id* プロパティです。
* `domain`：独自の Auth0 ドメインです。
* `packageIdentifier`： 独自の Ionic アプリケーションのウィジェット ID です。上記で説明したように、これはアプリケーションの `config.xml` ファイルにあります。

次に進む前に、`YOUR_CLIENT_ID`、`YOUR_AUTH_DOMAIN`、`your.app.id` とを独自のデータに置き換えてください。

### 認証サービス

Auth0 アカウントを作成して、`auth.config.ts` ファイルを定義したら、Ionic アプリの認証サービスを定義する必要があります。そのようなものとして、`auth.service.ts` という名前の新しいファイルを `./src/providers/auth0/` ディレクトリに作成し、そのファイルに次のコンテンツを加えます。

```typescript
import {Injectable, NgZone} from '@angular/core';
import {Storage} from '@ionic/storage';
import {Subject} from 'rxjs';
// AUTH_CONFIG、Auth0Cordova、auth0.js をインポートします
import {AUTH_CONFIG} from './auth.config';
import Auth0Cordova from '@auth0/cordova';
import * as auth0 from 'auth0-js';

@Injectable()
export class AuthService {
  Auth0 = new auth0.WebAuth(AUTH_CONFIG);
  Client = new Auth0Cordova(AUTH_CONFIG);
  accessToken: string;
  user: any;
  loggedIn: boolean;
  loading = true;
  isLoggedIn$ = new Subject();

  constructor(public zone: NgZone, private storage: Storage) {
    this.storage.get('profile').then(user => (this.user = user));
    this.storage.get('access_token').then(token => (this.accessToken = token));
    this.storage.get('expires_at').then(exp => {
      this.loggedIn = Date.now() < JSON.parse(exp);
      this.loading = false;
      this.isLoggedIn$.next(this.loggedIn);
    });
  }

  login() {
    return new Promise((resolve, reject) => {
      this.loading = true;
      const options = {
        scope: 'openid profile offline_access',
      };
      // Auth0 で認証ログインリクエスト： ログインページを開き、認証結果を取得します
      this.Client.authorize(options, (err, authResult) => {
        if (err) {
          this.loading = false;
          reject(err);
        } else {
          // アクセストークンと ID トークンを設定します
          this.storage.set('id_token', authResult.idToken);
          this.storage.set('access_token', authResult.accessToken)
            .then(() => {
              // ログイン済みに設定します
              this.loading = false;
              this.loggedIn = true;
              this.isLoggedIn$.next(this.loggedIn);
              resolve();
            });
          this.accessToken = authResult.accessToken;
          // アクセストークンの有効期限を設定します
          const expiresAt = JSON.stringify(
            authResult.expiresIn * 1000 + new Date().getTime()
          );
          this.storage.set('expires_at', expiresAt);
          // ユーザーのプロファイル情報をフェッチします
          this.Auth0.client.userInfo(this.accessToken, (err, profile) => {
            if (err) {
              throw err;
            }
            this.storage
              .set('profile', profile)
              .then(val => this.zone.run(() => (this.user = profile)));
          });
        }
      });
    });
  }

  logout() {
    this.storage.remove('profile');
    this.storage.remove('access_token');
    this.storage.remove('expires_at');
    this.storage.remove('id_token');
    this.accessToken = null;
    this.user = {};
    this.loggedIn = false;
    this.isLoggedIn$.next(this.loggedIn);
  }
}
```

上記のコード機能をよりよく理解するには、次の説明をお読みください。

* `accessToken`：これは JWT トークンで、ユーザーが Auth0 から取得します。これらトークンはユーザーを識別するために使用します。
* `user`：このプロパティは`email`、`firstname`、`lastname`、などのようなユーザーデータを保留します。
* `loggedIn`：このブール値はユーザーの認証状態を保留します。
* `isLoggedIn$`：これは RxJS サブジェクトです。これを `loggedIn` プロパティの最有効化バージョンとして考えてください。これはユーザーの認証状態を得るために Angular コンポーネントで使用できます。

では、上記のサービス方法を見てみましょう。

* `constructor()`：コンストラクターで、ユーザーが以前、認証したか否かを確認します。それを元に、`this.user` プロパティ、`this.accessToken` プロパティ、`this.loggedIn` プロパティの値を設定します。
* `login()`：ログイン方法では、ユーザーの認証が成功しているかそのユーザーを認証し、そのプロファイル情報をフェッチします。この情報を `@ionic/store` を介して永続記憶領域に保存し、認証状態を反映するサービスの適切なプロパティにも設定します。
* `logout()`：ログアウト方法では、ユーザーのすべての情報を永続記憶領域から削除し、ログアウト状態を反映するサービスのプロパティを設定します。

### 認証コールバック

認証後、Auth0 からのリダイレクトを処理するには、以下のように、`app.component.ts` ファイルを更新する必要があります。

```typescript
import { Component } from '@angular/core';
import { Platform } from 'ionic-angular';
import { StatusBar } from '@ionic-native/status-bar';
import { SplashScreen } from '@ionic-native/splash-screen';
import Auth0Cordova from '@auth0/cordova';

import { HomePage } from '../pages/home/home';
@Component({
  templateUrl: 'app.html'
})
export class MyApp {
  rootPage:any = HomePage;

  constructor(platform: Platform, statusBar: StatusBar, splashScreen: SplashScreen) {
    platform.ready().then(() => {
      statusBar.styleDefault();
      splashScreen.hide();

      // 認証した後に、アプリにリダイレクトします
      (window as any).handleOpenURL = (url: string) => {
        Auth0Cordova.onRedirectUri(url);
      };
    });
  }
}
```

## Ionic にオーディオプレーヤー UI を作成する

これまでは、フロントエンドやアプリケーションのユーザーインターフェイス（UI）に関係ないコードを記述してきました。このセクションでは、UI やその動作をデザインします。最終的に、アプリケーションは次のようになります。

![Ionic audio player demo app UI](https://images.ctfassets.net/23aumh6u8s0i/6PcyDhUH6qU84a5v74bdGG/5086bebbfb8fbdbddfcc0148ba3f24eb/final-app)

### オーディオプレーヤー HTML

`./src/pages/home/` ディレクトリ内で `home.html` ファイルを見つけます。このファイルにプレーヤーを定義する HTML を追加します。ご覧のように、スクリーンのトップにナビゲーション バーがあり、そのバーにはアプリケーションの名前とログアウトボタンがあります。このボタンはユーザーがログインしたときに、表示されます。

ヘッダーのほかに、 `ion-content` のログインボタン、アプリのロゴ、メディアファイルのリストが表示されます。

``` html  

<ion-header>
  <ion-navbar color="primary">
    <ion-title>Audio Player</ion-title>
    <ion-buttons end>
      <button *ngIf="loggedIn" ion-button icon (click)="logout()">Logout</button>
    </ion-buttons>
  </ion-navbar>
</ion-header>
<ion-content padding>
  <p *ngIf="auth.loading" text-center>Loading...</p>
  <ng-template [ngIf]="!auth.loading || !loggedIn">
    <div padding id="app-section" text-center>
      <ion-icon color="primary" name="musical-notes"></ion-icon>
      <h2 id="app-title">Audio Player</h2>
      <button outline ion-button block color="primary" *ngIf="!loggedIn" (click)="login()">Log In</button>
    </div>
  </ng-template>
  <ion-list *ngIf="files.length && loggedIn">
    <ion-list-header>Hello {{auth.user?.name}}</ion-list-header>
    <ng-container *ngFor="let file of files; let i = index">
      <ion-item text-wrap (click)="openFile(file, i)">
        <ion-icon color="primary" item-start name="musical-note"></ion-icon>{{ file.name }}
        <p item-end *ngIf="currentFile.index === i">SELECTED</p>
        <ion-icon item-end name="play" *ngIf="currentFile.index !== i"></ion-icon>
      </ion-item>
    </ng-container>
  </ion-list>
</ion-content>

```

Ionic アプリケーションのフッターには `ion-toolbar` が２つあります。

最初の `ion-toolbar` には、`ion-range` で作成された `seekbar` があります。これは、ユーザーが再生されるオーディオファイルの最新時刻を変更できるようにします。以下はその HTML です。

``` html  

<!-- ... ion-header と ion-content ... -->
<ion-footer *ngIf="currentFile.file && loggedIn" [@showHide]="displayFooter">
 <ion-toolbar color="primary">
    <ion-range min="0" color="light" [max]="state.durationSec" [formControl]="seekbar" (ionFocus)="onSeekStart()" (ionBlur)="onSeekEnd($event)"
      name="seekbar">
      <ion-label color="light" range-left>{{ state.time }}</ion-label>
      <ion-label color="light" range-right>{{ state.duration }}</ion-label>
    </ion-range>
  </ion-toolbar>
</ion-footer>

```

ふたつめの `ion-toolbar` には、次のような再生コントロールの残りがあります。

``` html  

<!-- ... ion-header と ion-content -->
<ion-footer *ngIf="currentFile.file && loggedIn" [@showHide]="displayFooter">
  <!-- ... シークバーコントロールはこちら-->
 <ion-toolbar color="primary" padding>
    <ion-grid>
      <ion-row align-items-center id="media-controls">
        <button clear ion-col ion-button [disabled]="isFirstPlaying()" (click)="previous()">
          <ion-icon color="light" name="skip-backward"> </ion-icon>
        </button>
        <button clear ion-col ion-button *ngIf="!state.playing" (click)="play()">
          <ion-icon color="light" name="play"></ion-icon>
        </button>
        <button clear ion-col ion-button *ngIf="!!state.playing" (click)="pause()">
          <ion-icon color="light" name="pause"></ion-icon>
        </button>
        <button clear ion-col ion-button [disabled]="isLastPlaying()" (click)="next()">
          <ion-icon color="light" name="skip-forward"></ion-icon>
        </button>
      </ion-row>
    </ion-grid>
  </ion-toolbar>
</ion-footer>

```

[_このファイルの最終バージョンはこちらをご覧ください_](https://github.com/auth0-blog/ionic-audio-player/blob/master/src/pages/home/home.html)。

### オーディオプレーヤーのスタイルを設定する

アプリの外観を多少向上させるため、以下のように、`home.scss` ファイルのスタイルを設定します（このファイルは `./src/pages/home/` の下にあります）。

```scss
page-home {
  #app-section {
    #app-title {
      color: color($colors, 'primary');
      text-transform: uppercase;
    }
    ion-icon {
      font-size: 15rem;
    }
  }
  #media-controls {
    button {
      ion-icon {
        font-size: 2.5rem;
      }
    }
  }
}
```

### オーディオプレーヤーの UI コントローラー

オーディオプレーヤーのユーザーインターフェイスをコントロールするには、次を担当するコントローラーを実装します。

1. アプリ―が開くと、ユーザーが認証されているか否かを確認します。
2. ユーザーが認証されていなければ、認証 UI が表示されます。
3. 認証プロセスの後、モックサービスからメディアファイルがフェッチされ、オーディオプレーヤーに表示されます。
4. それから、ユーザーは再生、一時停止、メディアファイルの切り替えなどメディア操作が可能になります。ユーザーがログインしたりログアウトしたりできるようにもします。
5. ユーザーがログアウトしたら、保存スペースから認証状態が削除され、そのログイン UI が表示されます。

オーディオプレーヤーを実装するデフォルトの `HomePage` を使用しているので、`HomePage` クラス内のほとんどのロジックを実装します。次のセクションでは、次のメソッドを実装していきます。

* `constructor`：このメソッドは `HomePage` のインスタンスを作成し、`isLoggedIn` サブジェクトに登録する
* `login`：このメソッドはユーザーのログインを可能にする
* `getDocuments`: このメソッドは音楽ファイルを読み込む
* `presentLoading`：このメソッドは読み込み画面を表示する
* `ionViewWillLoad`：このメソッドは画面を更新するメディアイベントにリスナーを追加する
* `openFile`：このメソッドは音楽 URL をフェッチし、それを `playStream` にパスする
* `resetState`：このメソッドは現在の音楽状態をリセットする
* `playStream`：このメソッドはアクションが Reducer にディスパッチされるように `audioProvider.playStream` に登録する
* `pause`：このメソッドはユーザーが再生を一時停止できるようにする
* `play`：このメソッドはユーザーが再生を再度開始できるようにする
* `stop`：このメソッドはユーザが再生を停止できるようにする
* `next`：このメソッドはユーザーが次の音楽に移動できるようにする
* `previous`：このメソッドはユーザーが以前の音楽に移動できるようにする
* `isFirstPlaying`：このメソッドは[前へ] ボタンの使用を禁止する
* `isLastPlaying`：このメソッドは[次へ] ボタンの使用を禁止する
* `onSeekStart` および `onSeekEnd`：これらのメソッドはシーク機能の使用中に使用する
* `logout`：このメソッドはユーザーのログアウトを可能にする
* `reset`：このメソッドは Ionic アプリの状態をリセットする

ただし、これらメソッドの実装にフォーカスする前に、UX アプリを拡張するアニメーションを追加できます。これらアニメーションはオーディオプレーヤーが `inactive` 状態や `active` 状態に切り替わるときに表示されます。簡単に説明すると、ユーザーがオーディオファイルを再生し始めると、アプリはユーザーに音楽コントロールを表示します。

このためには、`./src/pages/home/home.ts` ファイルを開き、そのコードを次に置き換えます。

```typescript
import {Component, ViewChild} from '@angular/core';
import {trigger, state, style, animate, transition } from '@angular/animations';
import {NavController, NavParams, Navbar, Content, LoadingController} from 'ionic-angular';
import {AudioProvider} from '../../providers/audio/audio';
import {FormControl} from '@angular/forms';
import {CANPLAY, LOADEDMETADATA, PLAYING, TIMEUPDATE, LOADSTART, RESET} from '../../providers/store/store';
import {Store} from '@ngrx/store';
import {CloudProvider} from '../../providers/cloud/cloud';
import {AuthService} from '../../providers/auth0/auth.service';
import {pluck, filter, map, distinctUntilChanged} from 'rxjs/operators';

@Component({
  selector: 'page-home',
  templateUrl: 'home.html',
  animations: [
    trigger('showHide', [
      state(
        'active',
        style({
          opacity: 1
        })
      ),
      state(
        'inactive',
        style({
          opacity: 0
        })
      ),
      transition('inactive => active', animate('250ms ease-in')),
      transition('active => inactive', animate('250ms ease-out'))
    ])
  ]
})
export class HomePage { }
```

> 未使用のインポートはもうすぐ必要になりますので、現時点では気にしないでください。

### `constructor`

これから、次の3つを実行します。

1. オーディオプレーヤーをコントロールするのに役立つプロパティを定義する
2. オーディオプレーヤーが音楽を読み込んだり、認証を管理したりするときに使用するすべてのコンポーネントを挿入する
3. `isLoggedIn$` サブジェクトに `subscribe` する

従って、`HomePage` クラスの定義を次のように更新します。

```typescript
// ... ステートメントをインポートします ...

@Component({
  // ... セレクター、templateUrl、など...
})
export class HomePage {
  files: any = [];
  seekbar: FormControl = new FormControl("seekbar");
  state: any = {};
  onSeekState: boolean;
  currentFile: any = {};
  displayFooter: string = "inactive";
  loggedIn: Boolean;
  @ViewChild(Navbar) navBar: Navbar;
  @ViewChild(Content) content: Content;

  constructor(
    public navCtrl: NavController,
    public navParams: NavParams,
    public audioProvider: AudioProvider,
    public loadingCtrl: LoadingController,
    public cloudProvider: CloudProvider,
    private store: Store<any>,
    public auth: AuthService
  ) {
    this.auth.isLoggedIn$.subscribe((isLoggedIn: any) => {
      this.loggedIn = isLoggedIn;
      if (isLoggedIn) {
        this.getDocuments();
      }
    });
  }
}
```

> `getDocuments` メソッドは気にしないでください。その実装はもうすぐ行います。

### `Login` メソッド

それでは、ユーザーがログインできるように、次のメソッドを `HomePage` に追加します。

```typescript
// ... ステートメントと @Component 宣言をインポートします ...
export class HomePage {

  // ... コンストラクター ...

  login() {
    this.auth.login()
      .then(() => { console.log('Successful Login'); })
      .catch(error => { console.log(error); });
  }
}
```

### `getDocuments` メソッド

では、画面で素晴らしいローダーを使って `getDocuments` メソッドを実装し（`presentLoading` メソッドを使って）、`cloudProvider` の `getFiles` メソッドを介してファイルを次のようにフェッチします。

```typescript
// ... ステートメントと @Component 宣言をインポートします ...
export class HomePage {

  // ...コンストラクターとその他のメソッド ...

  getDocuments() {
    let loader = this.presentLoading();
    this.cloudProvider.getFiles().subscribe(files => {
      this.files = files;
      loader.dismiss();
    });
  }

  presentLoading() {
    let loading = this.loadingCtrl.create({
      content: 'Loading Content. Please Wait...'
    });
    loading.present();
    return loading;
  }
}
```

### `ionViewWillLoad` メソッド

ご存知のように、Ionic には Angular のようにライフサイクルフックがあります。このライフサイクルフックのひとつが `ionViewWillLoad` です。このフックを使ってリスナーをメディア状態の変更に追加しますので、変更が検出されると、画面を更新できます。

リスニングのプロセスはこのライフサイクルフックのメソッド内にある NgRx ストアを使って達成されます。

```typescript
// ... ステートメントと @Component 宣言をインポートします ...
export class HomePage {

  // ...コンストラクターとその他のメソッド...

  ionViewWillLoad() {
    this.store.select('appState').subscribe((value: any) => {
      this.state = value.media;
    });

    // Ionic がフッターに対応するように Content Screen のサイズを変更します
    this.store
      .select('appState')
      .pipe(pluck('media', 'canplay'), filter(value => value === true))
      .subscribe(() => {
        this.displayFooter = 'active';
        this.content.resize();
      });

    // currentTime に基づいてシークバーを更新します
    this.store
      .select('appState')
      .pipe(
        pluck('media', 'timeSec'),
        filter(value => value !== undefined),
        map((value: any) => Number.parseInt(value)),
        distinctUntilChanged()
      )
      .subscribe((value: any) => {
        this.seekbar.setValue(value);
      });
  }
}
```

### `openFile` メソッド

ユーザーがメディアファイルをクリックすると、`openFile` メソッドが起動します。それから、このメソッドは選択された `file` の `url` で `playStream` メソッドを起動します。

本書では、このデータは以前に実装されたモックサービスからきます。後のアーティクルでは、バックエンド API から情報をフェッチするこのクラスをリファクターしていきます。

```typescript
// ... ステートメントと @Component 宣言をインポートします ...
export class HomePage {

  // ...コンストラクターとその他のメソッド ...

  openFile(file, index) {
    this.currentFile = { index, file };
    this.playStream(file.url);
  }
}
```

### `resetState` メソッド

これから実装する `playStream` メソッドはまず、`resetState` メソッドを介して現在のメディア状態をリセットする必要がありますので、次のように実装できます。

```typescript
// ... ステートメントと @Component 宣言をインポートします ...
export class HomePage {

  // ...コンストラクターとその他のメソッド ...

  resetState() {
    this.audioProvider.stop();
    this.store.dispatch({ type: RESET });
  }
}
```

このためには、`resetState` メソッドが現在実行しているメディアファイルを停止し、そのメディア状態をリセットする `RESET` アクションを送ります。

### `playStream` メソッド

それから、`playstream` メソッドは `AudioProvider`の `playStream` メソッドを起動します。プロバイダー上のこのメソッドは登録し、聞き始めるために使用する Observable を `canplay`、`playing`、などのようなメディアイベントに返します。

各特定のイベントを基にして、適切なペイロードでストア アクションを送ります。基本的に、これらアクションは現在の時間やメディアの期間のようなメディア情報を格納します。

```typescript
// ... ステートメントと @Component 宣言をインポートします ...
export class HomePage {

  // ...コンストラクターとその他のメソッド ...

  playStream(url) {
    this.resetState();
    this.audioProvider.playStream(url).subscribe(event => {
      const audioObj = event.target;

      switch (event.type) {
        case 'canplay':
          return this.store.dispatch({ type: CANPLAY, payload: { value: true } });

        case 'loadedmetadata':
          return this.store.dispatch({
            type: LOADEDMETADATA,
            payload: {
              value: true,
              data: {
                time: this.audioProvider.formatTime(
                  audioObj.duration * 1000,
                  'HH:mm:ss'
                ),
                timeSec: audioObj.duration,
                mediaType: 'mp3'
              }
            }
          });

        case 'playing':
          return this.store.dispatch({ type: PLAYING, payload: { value: true } });

        case 'pause':
          return this.store.dispatch({ type: PLAYING, payload: { value: false } });

        case 'timeupdate':
          return this.store.dispatch({
            type: TIMEUPDATE,
            payload: {
              timeSec: audioObj.currentTime,
              time: this.audioProvider.formatTime(
                audioObj.currentTime * 1000,
                'HH:mm:ss'
              )
            }
          });

        case 'loadstart':
          return this.store.dispatch({ type: LOADSTART, payload: { value: true } });
      }
    });
  }
}
```

### `pause` メソッド

`playStream` メソッドが起動したら、メディア再生が始まります。そのようなものとして、ユーザーは再生を一時停止するかもしれません。そのため、次のように `pause` メソッドを実装します。

```typescript
// ... ステートメントと @Component 宣言をインポートします...
export class HomePage {

  // ...コンストラクターとその他のメソッド ...

  pause() {
    this.audioProvider.pause();
  }
}
```

### `play` メソッド

ユーザーがメディアを再度、再生し始めるかもしれないというのも本当です。そのため、次を追加します。

```typescript
// ... ステートメントと @Component 宣言をインポートします ...
export class HomePage {

  // ...コンストラクターとその他のメソッド ...

  play() {
    this.audioProvider.play();
  }
}
```

### `stop` メソッド

それから、メディアを停止するには、次のメソッドを追加します。

```typescript
// ... ステートメントと @Component 宣言をインポートします ...
export class HomePage {

  // ...コンストラクターとその他のメソッド ...

  stop() {
    this.audioProvider.stop();
  }
}
```

### `next` メソッド

また、ユーザーを次の音楽に移動させるために、次のメソッドを定義します。

```typescript
// ... ステートメントと @Component 宣言をインポートします ...
export class HomePage {

  // ...コンストラクターとその他のメソッド ...

  next() {
    let index = this.currentFile.index + 1;
    let file = this.files[index];
    this.openFile(file, index);
  }
}
```

### `previous` メソッド

同様に、以前のトラックを再生するメソッドを提供する必要があります。

```typescript
// ... ステートメントと @Component 宣言をインポートします...
export class HomePage {

  // ...コンストラクターとその他のメソッド  ...

  previous() {
    let index = this.currentFile.index - 1;
    let file = this.files[index];
    this.openFile(file, index);
  }
}
```

### `isFirstPlaying` メソッドと `isLastPlaying` メソッド

それから、再生されている音楽がプレイリストの最初のトラックか、最後のトラックかを確認する２つのヘルパーメソッドが必要です。これらのメソッドを使って UI ボタンを無効にしたり有効にしたりします。

```typescript
// ... ステートメントと @Component 宣言をインポートします ...
export class HomePage {

  // ...コンストラクターとその他のメソッド ...

  isFirstPlaying() {
    return this.currentFile.index === 0;
  }

  isLastPlaying() {
    return this.currentFile.index === this.files.length - 1;
  }
}
```

### `onSeekStart` メソッドと `onSeekEnd` メソッド

ユーザーがシーク操作ができるようにもしたいので、ユーザーがシーク操作を始めると、`onSeekStart` メソッドを起動します。そこでは、ファイルが現在再生されているか否かを確認して、その情報を格納します。それから、オーディオファイルを一時停止します。

シーク操作が終わると、`onSeekEnd` メソッドを起動し、そこで、ユーザーが選択した時間をフェッチできます。シーク操作前にファイルが再生された場合、その再生を再開します。

```typescript
// ... ステートメントと @Component 宣言をインポートします ...
export class HomePage {

  // ...コンストラクターとその他のメソッド ...

  onSeekStart() {
    this.onSeekState = this.state.playing;
    if (this.onSeekState) {
      this.pause();
    }
  }

  onSeekEnd(event) {
    if (this.onSeekState) {
      this.audioProvider.seekTo(event.value);
      this.play();
    } else {
      this.audioProvider.seekTo(event.value);
    }
  }
}
```

### `logout` メソッドと `reset` メソッド

最後に、ユーザーが `logout` メソッドを介してログアウトできるようにします。それと同時に、アプリケーション全体をリセットする `reset` メソッドがあります。

```typescript
// ... ステートメントと @Component 宣言をインポートします ...
export class HomePage {

  // ...コンストラクターとその他のメソッド ...

  logout() {
    this.reset();
    this.auth.logout();
  }

  reset() {
    this.resetState();
    this.currentFile = {};
    this.displayFooter = "inactive";
  }
}
```

### `AppModule` クラスを更新する

最後のステップ、アプリを初めて使うことができる前に、`app.module.ts` ファイル内で必要なライブラリすべてをインポートし始めます。

```typescript
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ErrorHandler, NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { StoreModule } from '@ngrx/store';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';

import { mediaStateReducer } from '../providers/store/store';
import { AudioProvider } from '../providers/audio/audio';
import { CloudProvider } from '../providers/cloud/cloud';

import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
import { AuthService } from '../providers/auth0/auth.service';
import { IonicStorageModule } from '@ionic/storage';
```

それから、`NgModule` で必要な `declarations`、`imports`、`providers` をすべて定義して終えます。

```typescript
// ... ステートメントと jwtOptionsFactory 関数をインポートします ...
@NgModule({
  declarations: [MyApp, HomePage],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    HttpClientModule,
    IonicStorageModule.forRoot(),
    StoreModule.forRoot({
      appState: mediaStateReducer
    }),
    IonicModule.forRoot(MyApp)
  ],
  bootstrap: [IonicApp],
  entryComponents: [MyApp, HomePage],
  providers: [
    StatusBar,
    SplashScreen,
    AudioProvider,
    CloudProvider,
    AuthService,
    { provide: ErrorHandler, useClass: IonicErrorHandler }
  ]
})
export class AppModule {}
```

## Ionic オーディオプレーヤーを構築し、実行する

アプリ全体を実装した後、次の `ionic`コマンドを介して直接実行できます。

```bash
# iOS アプリの場合
ionic cordova run ios

# android アプリの場合
ionic cordova run android
```

<include src="JpTweetQuote" quoteText="モバイルオーディオプレーヤーを @Ionicframework、@angular、#RxJS、#NgRx を使って作りました!!!"/>

## 結論および次のステップ

本書では、Ionic を使ってモバイルオーディオプレーヤーを作りました。RxJS を使ってオーディオ再生機能を開発しました。そのほかに、アプリケーションの状態を管理する NgRx を使いました。また、モバイルアプリでユーザー認証を処理する Auth0 も使いました。これで、静的なオーディオコンテンツを使ったアプリケーションの初めてのバージョンを作り終えました。

次のアーティクルでは、オーディオプレーヤーに動的オーディオコンテンツを提供するために、Node.js や Google Cloud を使ってバックエンドを作ります。バックエンドをホストするサーバーを構成するときに時間をかけ過ぎないようにするため、Node.js アプリ向けのサーバーなしソリューション [_Web __タスク_](https://webtask.io/)を利用します。

本書をお楽しみいただけましたでしょうか。次回をお楽しみに！
