こんにちは。Diverse developer blogです。
今回は、Diverseが運営する「BAR DUNBAR」の店舗内に常設しているデジタルサイネージをシステム化したいという要望に対して普段はアプリやWebなどを主戦場としていたエンジニアがどのようにしてシステムを完成させたのかというお話をさせていただきます。
デジタルサイネージシステム完成イメージ
本記事を読み進めやすくするために、まずは完成した動画をご覧ください。 2台のディスプレイでは、通常動画が常に同じ再生位置で流れています。 そしてリモコンのボタンを押すと、両画面が同時にお祝い動画へ切り替わります。 この記事では、この仕組みをどう作ったのかを順を追ってお話しします。
BAR DUNBARについて
「BAR DUNBAR」はDiverseが運営する、お客様にお酒だけではなく、人とのつながりや共感を提供し “バーコミュニティ”という文化をより楽しんでもらうためのコミュニティバーです。
コミュニティバー「DUNBAR」をオープンしました | 株式会社Diverse
■ Instagram https://www.instagram.com/bar_dunbar/ ■ 食べログ https://tabelog.com/tokyo/A1316/A131601/13299543/ ■ 住所 東京都目黒区下目黒2-20-28 いちご目黒ビル 2F https://maps.app.goo.gl/ZjAQBnfRsU53Mug4A
目次
- 目次
- タスクに取り組む前の不安と予測
- 要件/要望のヒアリング
- 現地調査と開発言語の決定
- 動画の再生機能から着手
- 動画再生の落とし穴(2)
- 動画再生の落とし穴(3)
- 複数ディスプレイでの動画の再生位置の同期の手法
- お祝い動画の切り替えイベントの検知
- お祝い動画の切り替えイベントの他機種への伝播手法
- アプリサイズの肥大化に対応する
- インストールスクリプトの開発
- 通常動画素材の切り替え方式
- 今後の課題
- タスクに取り組むことで得られた学び
- 最後に
タスクに取り組む前の不安と予測
まず前提としてこのタスクに取り掛かるにあたり、完成までの道筋が全く想像できないというのが率直な感想でした。
デジタルサイネージといえばコンビニなどに常設されている店内動画であるとか、街中にある動画広告のようなイメージしかなく実際にどのような仕組みで運用されているのか理解できていないままタスクに取り組むこととなりました。
今思い出すと複雑に考えていたなと振り返るのですが、当時の私が話を聞いた時点で考えていたのは 管理画面や配信サーバーがあり、それらを介して現地の再生端末を制御するものなのでは? とイメージしていました。
要件/要望のヒアリング
タスクに取り組むことが決まり、チームメンバーも決まった翌日に早速現地でヒアリングを実施しました。
※その時点でのデジタルサイネージの運用はyoutubeにアップロードした動画を 左右それぞれのディスプレイで自動ループ設定にして流しつづけるという 原始的な運用方法がとられていました。
ヒアリングした結果出た要件/要望は以下の4つでした。
- 左右のディスプレイに流れている動画の時間軸を同期させたい(要望)
- お客さんの誕生日等の特別なイベント時に、乾杯のタイミングで通常動画から乾杯用の動画へすぐに切り替えたい(必須要件)
- 特別な動画に切り替える作業については調理や接客でとても忙しいバーテンダーさんが可能な限り負担なくできるものにしたい(強い要望)
- 常時流している通常動画を周年や季節が変わるタイミングで変更したい(要望)
この話を聞いた時点で「イベントが発生したタイミングで手動で動画をすぐに切り替えたい」という要件が入っていたため
- お祝い動画の再生をシームレスに切り替えられること
- 容量の大きい動画ファイルへネットワークを介さずにアクセスできること
- 動画キャッシュ機構の利用しやすさ(そもそもローカルに動画があればキャッシュを気にする必要も無くなる)
といった点を考えると、Webで構築するよりも、動画ファイルをローカルで扱いやすいアプリとして構築した方が相性が良いと考えました。
【今回の対象となるサイネージ用のディスプレイ】

現地調査と開発言語の決定
その前提を踏まえた上で、次に取り組んだのが、サイネージ用のディスプレイに私たちが独自に開発したアプリをインストールすることができるのか?という疑問を解決することです。
現地調査した結果、幸いにもディスプレイにはAndroid TV OSが搭載されており、Google Play Storeも存在していました。さらに開発者オプションも有効にすることができたため、ADB経由でサンプルアプリをインストールできることを確認できました。
【店舗に設置されているディスプレイのスペック】 - モデル: Sony BRAVIA Professional Display - 画面サイズ: 43インチ / 4K - OS: Android TV 11 - 映像処理チップ: Realtek RTD シリーズ (H.264 ハードウェアデコーダ内蔵) - GPU: ARM Mali - 設置台数: 2台 - 設置向き: 物理的に90°反時計回り回転で壁掛け
【開発者向けオプションの有効化】

実際にアプリのインストールが可能であると判断できたため、次に決めるべきことはアプリを開発するための言語をどうするのか?ということです。
結論からいうとDiverseで開発実績もあるFlutterを採用することにしました。 今回はAndroidアプリの開発ということで単一コードでマルチプラットフォームで動かせるというFlutterの核となるメリット自体は活かせないですが、社内にはFlutterに精通したメンバーが多数いるのと、知見も豊富なため、今後のメンテナンスも考慮し、Flutterを採用しました。
動画の再生機能から着手
まずはローカルにてAndroid EmulatorとAndroidの実機を用いて開発に着手しました。
動画の再生には video_player パッケージを利用しています。ただし一つ制約があり、店内に置かれているサイネージ用ディスプレイは物理的に90°反時計回りに設置されているため、動画をそのまま再生すると異なる向きで再生されてしまいます。
開発当初は動画の再生widgetをRotatedBox(quarterTurns: 1)で制御しようと考えていました。しかし動画レイヤーにはNative SurfaceViewを使う必要があるにもかかわらず、Flutter widgetの回転はNative SurfaceViewには伝播しないという制約があります。
そこで試行錯誤した結果、widgetの回転ではなく動画自体をFFmpegで90度時計回り(CW)に回転してlandscapeとして保存するという原始的な手法を採用することになりました。
動画再生の落とし穴(1)
ローカルでの動画の再生については一切問題なかったのですが、現地に赴き試してみると画面が真っ黒になりいつまで経っても動画が再生されないという現象が起こりました。
通常のアプリであればVideoPlayerControllerのinitialize()が失敗すれば何かしらのエラーメッセージがLogcatに出るはずです。しかしLogcatには何も出ず、デバッグの手掛かりが何も掴めないという不測の事態となってしまいました。
そこから数時間試行錯誤した結果、私達は一つの仮説に辿りつきました。
「Realtekハードウェアデコーダは高ビットレートのH.264ストリームと相性が悪いのではないか?」
というものです。
ためしにFFmpegで動画のビットレートを5Mbps程度に落としたところ仮説は見事に当たり動画が再生されることを確認できました。
「Realtek」とは半導体チップを作っている台湾の会社のことであり ディスプレイで使用しているチップ(Realtek RTD シリーズ)には 映像デコーダや音声処理、HDMI入力処理、CPU, GPUなどが全部詰まっています。 本記事では便宜上、このディスプレイに搭載されているRealtek製SoC/デコーダ周辺の挙動を まとめて「Realtek」と呼んでいます。
動画再生の落とし穴(2)
しかし動画は再生できるものの、Logcatには「MediaCodec.setOutputSurface: IllegalArgumentException」というエラーメッセージが永遠に出続け、安定して動画が再生されない現象が再現されました。
調査した結果Flutterが内部でtextureの再バインドを試みる際、Realtekデコーダが setOutputSurface() を拒否するためということが分かりました。
これを解決する策は video_player のVideoViewType.platformViewを使うことでした。
VideoPlayerController.file( file, viewType: VideoViewType.platformView, )
これでネイティブSurfaceViewに直接描画する形式になり、texture viewの再バインドは走らなくなりました。
動画再生の落とし穴(3)
まだまだ落とし穴はありました。Flutterの新レンダラーであるImpellerを有効にした状態でBRAVIAのMali GPU+Platform Viewを扱うと、動画の再生処理中に黒画面になってしまうことも分かりました。こちらはImpellerを無効にすることで、従来のレンダラーである Skia/GLES にフォールバックさせることで解決する事ができました。
<meta-data android:name="io.flutter.embedding.android.EnableImpeller" android:value="false" />
以上の3点の問題を解決することで、無事、サイネージ用のディスプレイで通常のループ動画を安定して流すことができるようになりました。
※ローカルでは上手くいっていることを現地で実際に試してみると、予期せぬトラブルが往々にして発生することを身をもって学ぶことができました。
複数ディスプレイでの動画の再生位置の同期の手法
複数のディスプレイで再生されている動画の再生位置を同じにしたいという要望を実現するために2つのアプローチを検討しました。
(1) 中央サーバー方式
1つ目は動画の再生を制御する中央サーバーを用意し、ディスプレイをスレーブとすることで、一元管理する手法です。
[店内 LAN]
│
┌────────────┴────────────┐
│ │
[管理サーバー] [BRAVIA #1]
(Raspberry Pi 等) (Flutter アプリ)
│ ↑
│ │ WebSocket
├─────────────────────────┤
│ │
↓ ↓
[BRAVIA #2] [BRAVIA #N]
(Flutter アプリ) (Flutter アプリ)
こちらの方式を採用すれば再生位置の同期だけではなく、再生する動画の種類を選択できることや、動画の切り替えなども管理画面を通していつでも制御できるという強力なメリットがあります。
しかし、サーバーの用意や別途管理画面を用意したり、アプリ側にもWebSocketによるイベントの制御を行う必要があり、今の規模を考えると少し大袈裟なシステムであると感じたため、このアプローチは見送ることになりました。
(2) NTP-based 分散方式
2つ目は各ディスプレイが独立してNTPに現在時刻を問い合わせ、共通の時計を基準として再生位置を決定する方式です。
ntp.nict.jp
|
(各機が独立に問い合わせ)
|
+-----------------+-----------------+
| | |
v v v
BRAVIA #1 BRAVIA #2 BRAVIA #N
ntp offsetを保持 ntp offsetを保持 ntp offsetを保持
(Flutter app) (Flutter app) (Flutter app)
| | |
+-----------------+-----------------+
こちらの方式が優れている点は外部依存がNTPサーバーだけになるので、構成が比較的シンプルになることです。ディスプレイの管理台数が2台と少ないため、店舗運用のシンプルさを優先し、NTP方式を採用することになりました。
動画の再生位置は (NTP現在時刻 mod 動画長) で決定します。
同期時刻の調整
NTP同期は構成としてはシンプルですが、実用上ほぼ同期しているように見せるには、いくつかの調整が必要でした。
(1) 外れ値の除去
まず NTP から返される時刻は、起動のタイミングによっては外れ値が混じることがあったので、アプリの起動時に ntp.nict.jp を 5 回叩き、その中央値をオフセットとすることで外れ値を除去する調整を入れました。
(2) 累積ドリフトの調整
また通常動画をループしていると微妙に端末時計に差分が発生し、累積的に位置ずれが発生するため30分ごとにNTPを再同期する調整もいれました。
*ドリフトとは、各端末の時計や再生位置が 時間経過とともに少しずつずれていく現象を指します。
(3) ドリフト調整の3段階補正
それでも微妙にずれることがあったので15秒ごとに「期待位置」と「実際位置」を比較し、差分に応じて補正する対応も入れました。
< 50ms: in sync (何もしない)50〜1000ms: ソフト補正 →setPlaybackSpeed(1.0 ± up to 2%)で緩やかに補正≥ 1000ms: ハード補正 → 即 seek + speed を 1.0 にリセット
ソフト補正で速度を数%変えても、視聴者にはほぼ気付かれないくらいの調整となります。ハード補正を行うと動画が一瞬だけ飛んだように見えますが動画をじっと注視していない限りは意外と気付かれない程度のものとなります。
(4) ループ境界での再同期
動画が末尾に到達して先頭に戻る瞬間に端末ごとに数ms〜数百msのズレが発生し、それが累積する現象もありました。これを検出するために、「再生位置が末尾5秒より後 → 先頭5秒より前」というジャンプを検知したら、その瞬間にNTP位置に再seekするロジックも入れています。
計測結果
| 経過時間 | expected | actual | diff | 状態 |
|---|---|---|---|---|
| 00:00:00 | — | — | — | Initial sync: seek to 148,230ms (of 352,233ms) |
| 00:00:15 | 163,241 | 163,229 | +12 ms | in sync |
| 00:02:30 | 298,241 | 298,219 | +22 ms | in sync |
| 00:05:00 | 95,775 | 95,793 | −18 ms | in sync |
| 00:07:30 | 245,775 | 245,750 | +25 ms | in sync |
| 00:10:00 | 43,309 | 43,317 | −8 ms | in sync |
| 00:12:30 | 193,309 | 193,322 | −13 ms | in sync |
| 00:15:15 | 338,309 | 338,243 | +66 ms | speed=1.0044 (soft) |
| 00:17:30 | 128,843 | 128,830 | +13 ms | in sync |
| 00:20:00 | 278,843 | 278,857 | −14 ms | in sync |
| 00:22:30 | 76,377 | 76,349 | +28 ms | in sync |
| 00:25:00 | 226,377 | 226,371 | +6 ms | in sync |
| 00:27:30 | 24,411 | 24,390 | +21 ms | in sync |
| 00:30:00 | 174,411 | 174,415 | −4 ms | in sync |
【実測精度】 これらの調整を入れた状態で30分連続稼働させて計測すると位置ずれの平均数値は19〜48msとなり 一般的な体感として違和感が出にくい目安である100ms未満に抑える事ができました。
お祝い動画の切り替えイベントの検知
デジタルサイネージシステムの必須要件として通常動画からお祝い動画へ切り替えられるというものがあり、かつ「調理や接客でとても忙しいバーテンダーが、IT操作を意識せずに、限りなく少ない負担で操作できるようにしたい」という要望もありました。
その話を聞いたときに複数のアイデアが思い浮かびました。
- 調理場にある管理用のiPadに動画切り替え用のショートカット/アプリ/Webページを作成してタップしてもらう
- 🔺画面切り替え、タップやスワイプ操作が発生し、思っているよりかは簡単ではなさそう
- バーテンダーさんが身につけているSmartwatch用のアプリを作成して、そのアプリをタップしてもらう
- 🔺Smartwatch用のアプリ開発/運用コストが発生、Smartwatchを瞬時に操作するのは意外に難しい、普段使いのSmartwatchを使うため機種の限定が難しい
- AmazonDashのような専用の物理ボタン機器を購入し、ボタンを押してもらう
- ❌外部機器購入コストの発生、専用ボタンの追加開発が必要、物理デバイスに依存するので気軽に試すことができない。
どの手法も、可能性としてはありだが、簡単に試すことができず、またバーテンダーさんの負担を限りなく抑えるという要望に応えるには不十分であり、もっとも頭を悩ませたところです。
テレビのリモコンは使えないのか? - 正解は意外と身近に存在する
イベントの検知手法について頭を悩ませていたところ、突然アイデアが舞い降りてきました。
「例えばテレビを見ているときに、地震が発生して、リモコンでニュース番組に切り替えるくらいの感覚で操作できればよいのではないか?」
リモコンが使えるという発想は元々なかったのですが、Android TV OSであること、チームメンバーと話している中でbluetoothを使えばいいのでは?というような話がでていたこと、操作の容易性を考えると結局は物理ボタンがいいと考えていた事、これらの思考が有機的につながり、最終的には最も身近にあったリモコンを使うという発想に至ることができました。リモコンが使えるのであれば外部機器の追加購入も不要であり、イベントの送信部分に関しても追加で実装する必要はなくなります。
さっそく実装の調査を開始したところHardwareKeyboard.instance.addHandlerを利用することにより最下層のレイヤーで全てのキーをキャッチできることが分かりました。さらにリモコンのいいところはチャンネル切り替えのための数字も使え、色ボタンもキャッチすることができるので、押すボタンによってお祝い動画の内容も変えるということの実現も容易になったということです。
お祝い動画の切り替えイベントの他機種への伝播手法
次に一方をお祝い動画に切り替えたらもう一方が連動して切り替わる仕組みの検討をすることにしました。もちろん原始的には一方をリモコンで切り替えて、続けてもう一方をリモコンで切り替えるという手法を取ることはできます。しかし調理や接客で忙しいバーテンダーさんが物理的に切り替える作業が必要なため、どれだけ早く操作しても1秒〜3秒のずれはどうしても発生してしまうこととなります。結果的に特別なイベントの演出の一体感を損なうことにも繋がりかねないため、できるだけ同時のタイミングで切り替えられる方式を模索することとなりました。
(1) WebSocket方式
まず検討したのはFirebase Realtime Database等の外部WebSocketサービスを利用し、各ディスプレイがイベントを常時subscribeする方式です。
【WebSocket方式】
[リモコン (数字キー押下)]
|
v
BRAVIA #A
+--------------------------+
| HardwareKeyboard が捕捉 |
| -> Firebase RTDB に write|
| -> ローカルでも event |
| 動画を発火 (同期) |
+------------+-------------+
|
| HTTPS / WebSocket
| write: /events/current = {
| "event_id": "cheers",
| "triggered_at": <ts>
| }
v
============= [インターネット] =============
|
v
+-----------------------+
| Firebase Realtime DB |
| (Google Cloud) |
+-----------+-----------+
|
| subscribe 中の全機に push
| (WebSocket)
v
============= [インターネット] =============
| |
v v
BRAVIA #B BRAVIA #C
+----------------+ +----------------+
| onValue 発火 | | onValue 発火 |
| ↓ | | ↓ |
| event 動画発火 | | event 動画発火 |
+----------------+ +----------------+
結果: 全機が同期して event 動画に切替
(インターネット往復ぶんのレイテンシ: 50〜500 ms)
この構成は現実的ではありましたが、以下の懸念点がありました。
- プロダクトの規模に対して外部サービスを経由させる必要性が薄い
- インターネットを往復させるため50-500ms程度のレイテンシがどうしても発生してしまう
- 一方のアプリでリモコンイベントの受信後、イベントの書き込み処理 → もう一方がその書き込みを検知する仕組みが必要となり、想定よりも実装が複雑になる
これらの理由により、WebSocketによる実装は一旦断念することとなりました。
(2) UDPブロードキャスト方式
次に検討したのはUDPブロードキャストを利用するという方式です。
UDPブロードキャストは、同じLAN上のすべての機器に、一斉に同じデータを送る仕組みとなります。送信側は個別の宛先を知らなくても、"ブロードキャストアドレス" (255.255.255.255 など) 宛に送ることで、そのネットワーク上の全機に届かせることができます。任天堂Switchのローカル通信であったり、ネットワーク機器がLANに繋がったときに「DHCPサーバーいませんか?」というような信号をブロードキャストで叩いているなど、身近な機器でも利用されている一般的な技術となります。
構成としては以下のようになります。
【UDPブロードキャスト方式】
[リモコン (数字キー押下)]
|
v
BRAVIA #A
+--------------------------+
| HardwareKeyboard が捕捉 |
| -> UDP broadcast 送信 |
| -> ローカルでも event |
| 動画を発火 (同期) |
+------------+-------------+
|
| UDP send
| 宛先: 255.255.255.255 : 5556
| payload: "event:xxxxxx"
v
================ [店内 LAN] ================
| |
v v
BRAVIA #B BRAVIA #C
+---------------+ +---------------+
| listen socket | | listen socket |
| ↓ | | ↓ |
| event 動画発火 | | event 動画発火 |
+---------------+ +---------------+
結果: 全機が同時に event 動画に切替
(LAN 内なのでレイテンシは 数 ms 以下)
デジタルサイネージシステムにおいてUDPブロードキャスト方式が優れている点は
- 追加機器の購入/外部サービスが不要であり、気軽に試すことができる
- レイテンシは同じLAN内で通信することにより数ms以下に抑えることができる
- 実装も比較的シンプル
というものがあります。ただしTCPとは異なり、到達保証や順序保証がないというデメリットもありますが、今回の用途においては許容できると判断したためUDPブロードキャスト方式を採用することになりました。
アプリサイズの肥大化に対応する
当初からの方針として、再生する動画はアプリの中にバンドルした上で参照することにしていました。ただし実際にバンドルしてみると通常動画が300MBもあったり、それに加えてお祝い動画も追加すると合計で500MBの巨大なアプリとなってしまいました。これでは開発 → deployというサイクルを回すたびに10分の待ち時間が発生してしまうこととなります。
肥大化に対応するために、動画自体はディスプレイ本体の内部ストレージにコピーした上でアプリはそのコピーを参照するという方式に変更したところ最終的には40MB程度に抑えることができました。
権限の落とし穴
「動画を端末ストレージに置く」だけなら簡単そうですが、Android 11以降のscoped storage制約が落とし穴でした。
adb shell mkdir -p xxx
で作成したディレクトリはshellユーザーの所有となります。この状態でファイルを配置するとアプリ側から
PathAccessException: Directory listing failed (OS Error: Permission denied, errno = 13)
というエラーがでて読めませんでした。ディレクトリがshellユーザーの所有であるために権限がないということですね。
解決策としては動画を配置する前にアプリ側でディレクトリをapp所有で作った後に、adb pushで動画を配置するという方式をとることで解決できました。
インストールスクリプトの開発
これらの手順を踏みながらアプリのインストール作業を行うことは煩雑となるため
adb/ffprobe等のツールのインストールチェック- 実際に転送する動画がassets/videos以下に存在するのかをチェック
- ffprobeによってコーデック/解像度/ビットレートを自動でバリデーションする
- APKをビルド/インストールする
- インストールしたアプリを一瞬起動し、アプリの所有権限でディレクトリを作成する
- 動画をadb pushする
これら一連の作業を自動で行なってくれるツールを作成することにより、難しいことを考えなくとも一気通貫でインストール作業を完了できるようにしました。
通常動画素材の切り替え方式
要望の一つとして通常動画も季節やイベントによって切り替えたいというものがありました。
こちらはディスプレイ側に動画をあらかじめコピーしておき、アプリ側で月・日・時と動画ファイルのマッピング定義をアプリ側に持たせ、再生する動画をそのマッピング定義により決定するというロジックで実装しています。
class LoopScheduleEntry { final int month, day, hour; final String filename, label; } const List<LoopScheduleEntry> kLoopSchedule = [ LoopScheduleEntry(month: 1, day: 1, hour: 0, filename: kDefaultLoopFilename, label: 'default'), LoopScheduleEntry(month: 7, day: 7, hour: 0, filename: kAnniversaryLoopFilename, label: 'anniversary'), LoopScheduleEntry(month: 7, day: 13, hour: 2, filename: kDefaultLoopFilename, label: 'default'), ];
これにより、あらかじめ配置済みの動画の範囲であれば、周年や季節に応じた切り替えを自動化できるようになりました。
今後の課題
ただし、通常動画も固定するのではなく、更新したいという要望も追加で発生しているため、どうすれば簡単に更新作業が行えるのかという点についてはこれからの課題として残っています。
タスクに取り組むことで得られた学び
(1) シミュレーションでは完璧だったことが現場ではうまくいかないことが往々にしてある
本文でも触れていますが、ローカルで開発し、様々な検証を行い、準備万端の状態で店舗へ赴き、予測では数分で終わるだろうと思っていたことが、動画の再生が思うようにいかなかったり、予期せぬエラーに見舞われたりと実際には順風満帆に進行することはありませんでした。
準備は万全だったとしても、現場ではエラーが発生するという心構えをもって臨むということが大切だと学びました。
(2) 課題を解決する方法は意外とシンプル
タスクに取り組む前は、複数の外部サービスの利用や外部機器の追加購入をし、ネットワークを介して複雑なシステムを開発するようなものをイメージしていました。
しかし最終的に完成した設計は外部サービスを使わず、外部機器の追加購入も不要であり、外部ネットワークの利用も最低限なシンプルな設計にすることができました。
複雑に考えていたことが、一つ一つの要素を分解すると最終的には意外とシンプルな設計に落ち着くこともあることを学びました。
(3) AIは強力だが全てを解決しない。もっとも重要な部分は人間の発想が決め手になることもある
本プロジェクトはAIと相談しながら進行し、また実装についてもAIの力を借りています。比較的つまずくこともなく進行できたこと、また未知の技術に取り組めたことはAIの力によるところが大きいです。
ただし、AIは基本的には人間が入力した情報や質問によってアウトプットが左右される一面があり、人間が現場での状況や情報を全てAIに与えるのは実質的には不可能であり、それによってアイデアの元になる情報が欠けてしまうことがあります。
それに対して人間は現場での情報を自然に把握していたり、あえて言語化していない情報も無数に持っています。
リモコンを最終的なイベント送信デバイスとして利用できないか?という件はAIとの壁打ちをしている中では一切登場しないまま発想したものなので、人間の観察や発想の中から生まれてきたものだと考えています。(その発想に至ってからはイベント送信デバイスとしてのリモコンについてAIと壁打ちし、実装までをスムーズに進行することができました。ありがとうございます😊)。
最後に
普段開発しているサービスでは実装した機能が実際にユーザーにどのように使われているのか?とか、どのように喜ばれているのかということを生の反応として知るのが構造的に難しいという一面があります。
デジタルサイネージシステムは実店舗で運用され、お客さんの反応も即座に得られ、開発者と利用者の距離が近いので普段の開発とはまた違った開発体験を得ることができました。
今後もバーという実店舗と様々な技術を組み合わせることで店舗に訪れてくれたお客さんの満足度を高めるような施策に取り組んでいきたいと考えています。











