kintoneでミュージックプレイヤーを作ってみた

🎄こちらは [kintone Advent Calendar 2018](https://qiita.com/advent-calendar/2018/kintone) **23日目** の記事です。

kintone Advent Calendar 2018 オーナーのBBです。
まずは、kintone Advent Calendarに参加してくださったみなさまへ感謝の気持ちを。
**参加していただき、ありがとうございます！！**
（来年も開催したいと思っているのでぜひご協力を！ めざせ Part2！）

# はじめにの前に

このkintone Advent Calendarを立ててすぐに
初日でもなく最終日でもなくこの **23日** を選んだのには理由があります。

それは、、
<br/><br/><br/><br/>
23日が **My Birthday** だからです🎉🎉
<br/><br/><br/><br/>
え？ それだけかって？

はい、それだけです。

# はじめに

では気を取り直して、

「ネタ系で頑張る」と最初から宣言してしまった以上、ネタで攻めさせていただきました。
タイトルからも分かる通り、「え？それkintoneでやる必要あるの？」って内容です 笑

ただ、普通にやるだけではつまらないので
勉強も兼ねて今流行りの **「Vue.js」** を使ってみました。

# 完成イメージ
![kinMusic.gif](https://qiita-image-store.s3.amazonaws.com/0/153051/27ecbf26-a5c9-400a-e299-e9e9adbe2abf.gif)
※ 小さくてすみません m(\_ _)m 
※ そして僕の趣味丸出しですみません m(\_ _)m

GitHubPagesにも同じやつを作ってみました。
https://rybb.github.io/MusicPlayer/
※ 著作フリーなやつに差し替えています。ついでにクリスマスっぽくしています☃

# きっかけ

なぜこのカスタマイズをしようと思ったかというと、

- 2018年12月版で搭載された**モバイルカスタマイズビュー**を使ってみたかった
- **業務以外**でのkintoneの使い道を模索したかった
- 去年あたりにも、実は音楽プレイヤー化に挑戦していた

という理由があります。
一番の理由はやはり 「モバイルカスタマイズビューを使ってみたかった」 ですね！

# 仕組み

仕組み自体はとてもシンプルです

1. モバイルの一覧表示イベント(mobile.app.record.index.show)で発火🔥
2. レコードを全件取得する （GET,records）
3. 画像ファイル、音楽ファイルの **Blob URL** を取得する
4. HTMLタグにそれらを埋め込む
5. 下に全件リスト表示させて、クリックしたらTOPと変更する

みたいな動きです。

## Blob URL

kintoneでは添付ファイルはBlob形式で保存されています。
なのでこのBlob URLを作ることで、いわゆる **直リンク** が生成できるわけです。

Blob URLの取得方法は cybozu developer network に記事があります！
https://developer.cybozu.io/hc/ja/articles/202166180

# アプリの作り方

では、さっそく作り方のご紹介を。

フィールドはこんな感じです

|フィールド|フィールドコード|用途|
|---|---|---|
|添付ファイル|image|画像ファイル用|
|添付ファイル|music|音楽ファイル用|

めっちゃシンプル

# コード

[GitHub](https://github.com/RyBB/kintoneCustomize/tree/master/MusicPlayer) にもあげています。

```html:カスタマイズビュー用HTML
<style>
.main {
  max-width: 500px;
  margin: auto;
  text-align:center;
}
.title {
  margin: 1em;
  cursor: pointer;
  border-bottom:1px solid #555555;
}
#img {
   height: 300px;
   width: auto;
}
#list {
  font-size: 1.5em;
  margin: 0.5em;
}
audio {
  width: 100%;
}
</style>
<body>
<div class=bg>
  <div class="main">
    <div id="app">
      <div class="wrapper">
        <div>
          <img id="img" :src="imglink">
        </div>
      </div>
      <div>
        <a><audio :src="musiclink" v-on:ended="nextMusic" autoplay controls></audio></a>
      </div>
    </div>
    <ol id="list">
      <li class="title" v-for="item in items" v-on:click="changeMusic(item)">
        {{ item.music.value[0].name }}
      </li>
    </ol>
  </div>
</div>
```

```javascript:app.js
// イベント発火とかメイン処理

(() => {
  'use strict';
  const mykintone = window.mykintone;

  const events = [
    'app.record.index.show',
    'mobile.app.record.index.show'
  ];
  kintone.events.on(events, async e => {
    // カスタマイズビュー以外なら終了
    if (!e.viewId === 5740139) {
      return;
    }

    // kintoneのレコードを全件取得する
    const kinData = await mykintone.getRecords(e.appId);
    const records = kinData.records;

    // 添付ファイルの直リンク生成
    const URL = await mykintone.getImageMusicURL(records[0]);

    // Vue.js
    const app = new Vue({
      el: '#app',
      data: {
        index: URL.index,
        imglink: URL.img,
        musiclink: URL.music,
      },
      methods: {
        nextMusic: async function() {
          let newURL;
          const index = records.findIndex(obj => {
            return  obj.$id.value === this.index;
          });
          if (!records[index + 1]) {
            newURL = await mykintone.getImageMusicURL(records[0]);
            app.index = newURL.index;
            app.imglink = newURL.img;
            app.musiclink = newURL.music;
            return;
          }
          newURL = await mykintone.getImageMusicURL(records[index + 1]);
          app.index = newURL.index;
          app.imglink = newURL.img;
          app.musiclink = newURL.music;
        }
      }
    });

    new Vue({
      el: '#list',
      data: {
        items: records
      },
      methods: {
        changeMusic: async function(event) {
          let newURL = await mykintone.getImageMusicURL(event);
          app.index = newURL.index;
          app.imglink = newURL.img;
          app.musiclink = newURL.music;
        }
      }
    });
  });
})();

```

```javascript:kin.js
// kintone API系の処理

(() => {
  'use strict';

  // kintoneのフィールドコードとか
  const common = {
    image: 'image',
    music: 'music'
  };

  const xhr = new XMLHttpRequest();

  // レコードを全件取得する処理
  const getRecords = APPID => {
    return kintone.api(kintone.api.url('/k/v1/records', true), 'GET', {app: APPID})
    .then(resp => {
      return resp;
    })
    .catch(err => {
      console.log('Error:kintone\n' + err);
    });
  };

  // 添付ファイルの直リンクを作る関数
  const filedownload = filekey => {
    return new Promise((resolve, reject) => {
      const url = '/k/v1/file.json?fileKey=' + filekey;
      xhr.open('GET', url, true);
      xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
      xhr.responseType = 'blob';
      xhr.onload = () => {
        // blobからURL生成
        let blob = xhr.response;
        let url = window.URL || window.webkitURL;
        let file = url.createObjectURL(blob);
        resolve(file);
      };
      xhr.onerror = () => {
        reject('Error:XHR');
      };
      xhr.send();
    });
  };

  // 画像ファイルと音楽ファイルの直リンク生成
  const getImageMusicURL = DATA => {
    return new Promise(async resolve => {
      const index = DATA.$id.value;
      let imageURL, musicURL;

      // 画像ファイルの有無チェック
      if (!DATA[common.image].value[0]) {
        imageURL = null;
      } else {
        let imageFileKey = DATA[common.image].value[0].fileKey;
        imageURL = await filedownload(imageFileKey);
      }

      // 音楽ファイルの有無チェック
      if (!DATA[common.music].value[0]) {
        musicURL = null;
      } else {
        let musicFileKey = DATA[common.music].value[0].fileKey;
        musicURL = await filedownload(musicFileKey);
      }

      resolve({
        index: index,
        img: imageURL,
        music: musicURL
      });
    });
  };

  window.mykintone = {};
  window.mykintone.getRecords = getRecords;
  window.mykintone.getImageMusicURL = getImageMusicURL;
})();

```

結構思うがままに書いていたので読みにくいかもです m(_ _)m

## ポイント

結構大変でした。

- 画像ファイルは \<img>のsrcに埋め込めば表示される
- 音楽ファイルは \<audio>タグのsrcに埋め込めば、操作バーとして表示される
- Vue.jsでリスト表示や再生ファイルの入れ替えを実現
- カスタマイズビューに\<style>タグ使えば、CSSも書ける
- 音楽が終わったら次の曲を再生させている

Vue.js自体全然知らなかったので、ググりながら試してみた感じです
(Vue使う必要ある？って思うかもですが、、、これは僕の勉強のためです！ 汗)

## 発展

以前、Qiitaに投稿したGoogleの画像検索API連携を使えば、
「歌手」「アルバム名」などを入力するフィールド追加で
**自動で画像URLを取得** することもできそうです。

**kintoneで画像検索API使ってみた**
https://qiita.com/RyBB/items/b87a92815854ed5c74af


## 小ネタ

実はkintoneモバイル (モバイルアプリ)で再生すれば、
**バックグラウンド再生** ができるんです！！

まさに音楽プレイヤー！

# おわりに

Vue.jsの使い方がまだまだイマイチですね。もう少しちゃんと勉強せねば、と思いました
(何度、「もうjQueryで書こうかな」と揺らいだものか)

まぁそれは置いといて、
kintoneは業務で使うだけでなく、こんなお楽しみ？要素としても使えます。
複数人で同じ音楽ファイルを聞けるので、ストリーミング的な使い方もできそうですね
※ kintoneのデータ容量にはご注意ください

それでは！≧(+･` ཀ･´)≦