0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ReactでRuffleを使ってFlashを再生する

Last updated at Posted at 2021-06-11

使いどころ

遺産 Flash ファイル(*.swf)を React などの SPA で使いたいとき!
当然、Flash Player も頼れない。
React + Ruffle + *.swf または、フロントWebフレームワーク + Ruffle + *.swf

公開に至るまで

Adobe が Flash Player のサポートを完全終了してから久しいですね。
あれは、2020年12月31日の出来事でした…。

遺産 Flash ファイル(*.swf)を再生する方法は、未だに需要があります。
動画内にクリックできる部分が必要だったりして、
単純にMP4とかに動画にすれば済むわけでもありません。
かといって、HTML5+CSS3アニメーションで1から作るのも面倒です。

そのため Ruffleとか、swf2js を検証してみました。
静的ページなら Ruffle の公式ドキュメントを見れば簡単です。
しかし動的ページや SPA だと、そうはいきません。

  • URL が切り替わると再生されない!
  • Rust から自分でコンパイルすればいいらしい?
  • wasm-loader が要るらしいけど、webpack なの? create-react-app なの?

みたいな感じで大混乱です。

再生する方法を模索していて、かなり簡単な解決策を見つけたので紹介します。

方法

SPA内コンテンツが描画されるとき、動的にカスタムイベントを発生させます。
そのカスタムイベント内で、Flash の再生を開始させます。

キーワードは、下記4つです。

  • componentDidMount
  • dispatchEvent
  • addEventListener
  • CustomEvent

※後述する例は React + TypeScriptです。しかし React + JavaScript や他のフロント Web フレームワーク構成でも、少しの書き換えで済むと思います。

ruffle.js内で*.wasmファイルを読み込みます。wasmのサイズが大きいため、これを最適化したい人には不向きな方法です。そういう時はRustからコンパイルしたほうが良さそう。

※この方法はRuffle の公式ドキュメントに書かれている、Website Self Hosted の延長線上にある手法です。Rust開発環境は不要です。

構成

┣━public
┃    ┣━ flash
┃    ┃    ┗━ flash
┃    ┃        ┗━ legacy.swf
┃    ┗━ ruffle
┃         ┗━ ruffle-nightly-2021_05_17-web-selfhosted
┃             ┣━ ???.wasm
┃             ┣━ ...
┃             ┗━ ruffle.js
┗━src
    ┣━ components
    ┃    ┣━ fixed_mainnav.tsx
    ┃    ┣━ header.tsx
    ┃    ┣━ main.tsx
    ┃    ┗━ maincontent.tsx
    ┣━ index.html
    ┗━ index.tsx

maincontent.tsx内の JSX が読み込まれたときに、毎回legacy.swfを再生します。

コード

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
  <title>React App</title>
  <script src="%PUBLIC_URL%/ruffle/ruffle-nightly-2021_05_17-web-selfhosted/ruffle.js"></script>
</head>
<body>
  <div id="root"></div>
</body>
<script>
  var config = {
    // Options affecting the whole page
    "publicPath": "Failed to load the video!",
    "polyfills": true,
    // Options affecting files only
    "autoplay": "true",
    "unmuteOverlay": "hidden",
    "backgroundColor": null,
    "letterbox": "fullscreen",
    "warnOnUnsupportedContent": true,
    "contextMenu": false,
    "upgradeToHttps": window.location.protocol === "https:",
    "logLevel": "error",
  };

  function loadflash(elementId,flashUrl) {
    const ruffle = window.RufflePlayer.newest();
    const player = ruffle.createPlayer();
    const container = document.getElementById(elementId);
    container.appendChild(player);
    player.config = config;
    player.load(flashUrl);
  }
  function loadflash001() {
    loadflash('flash001','%PUBLIC_URL%/flash/legacy.swf');
  }
  window.addEventListener("playflash", loadflash001);
  window.addEventListener("load", loadflash001);
</script>

</html>

headタグ内で、下記のように、ruffle.jsを読み込んでいます。

  <script src="%PUBLIC_URL%/ruffle/ruffle-nightly-2021_05_17-web-selfhosted/ruffle.js"></script>

loadflashが Flash を再生させる関数です。

最終的にloadイベントとplayflashイベントで Flash を再生させようとしています。
なぜloadのほうに登録が必要かと言うと、URL 直打ちでplayflashイベントが発生できなかったからです。
※この辺り細かく検証できていなくて、分かる方いたらコメントいただけると嬉しいです。

直接loadflashaddEventListenerしてみましたが、
loadイベント時にplayflashイベントも同時発生することが分かりました。
そのため、loadflash001関数を使って、あえて2段編成にしています。

index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import './index.sass';
import reportWebVitals from './reportWebVitals';
import MainApp from "./components/main";
import HeaderApp from "./components/header";
import FooterApp from "./components/footer";
import { createBrowserHistory } from "history"
import { Router } from 'react-router';

const history = createBrowserHistory({ basename: '/new_web' });
ReactDOM.render(
  <React.StrictMode>
    <Router history = {history}>
      <HeaderApp></HeaderApp>
      <MainApp></MainApp>
      <FooterApp></FooterApp>
      </Router>
  </React.StrictMode>,
  document.getElementById('root')
);
reportWebVitals(console.log);
main.tsx
import React from 'react';
import { Route, Switch } from 'react-router-dom';
import OutlineApp from './outline';
import MainContentApp from './maincontent';

class MainApp extends React.Component {
    render() {
        return (
            <main id="wrap_main">
                <div id="sidearea"></div>
                <div id="wrap_contents">
                    <Switch>
                        <Route exact path="/" component={MainContentApp} />
                        <Route exact path="/outline/" component={OutlineApp} />
                        <Route render={() => <p>NotFound</p>} />
                    </Switch>
                </div>
                <div id="main-right-spacer"></div>
            </main >
        );
    }
}
export default MainApp;

上記のmain.tsxの中で、

<Route exact path="/" component={MainContentApp} />
<Route exact path="/outline/" component={OutlineApp} />

という部分があります。ここで、URL が/に遷移した時には、MainContentAppが読み込まれます。
そこには、Flash を再生させたいタグid='flash001'があります。

しかし、URL が/outline/に遷移した時には、MainContentAppの代わりにOutlineAppが読み込まれます。
そのため、タグid='flash001'は一度無くなります。

その後、再びURL が/に遷移すると、もう一度MainContentAppが読み込まれます。
タグid='flash001'が、再度出現することになります。

この時、window.addEventListener("load", func)だけだと、Flash は再生されません。
loadイベントは、初めて DOM を読み込んだ時にしか発生しないからです。

maincontent.tsx
import React from 'react';
let FlashEvent = new CustomEvent('playflash', { bubbles: true });

class MainContentApp extends React.Component {
    componentDidMount() {
        window.dispatchEvent(FlashEvent);
    }
    render() {
        return (
            <React.StrictMode>
                <div id="flash001"></div>
            </React.StrictMode>
        );
    }
}
export default MainContentApp;

上記maincontent.tsx<div id="flash001"></div>があります。ここにFlashを読み込みます。

また、maincontent.tsxでは2行目で、下記のようにplayflashイベントを宣言しています。

let FlashEvent = new CustomEvent('playflash', { bubbles: true });

そして、このMainContentAppコンポーネントがマウントされると、

    componentDidMount() {
        window.dispatchEvent(FlashEvent);
    }

によって、FlashEventがディスパッチされます。
これにより、index.htmlで用意した、playflashのイベントリスナーがキックされます。

header.tsx
import React from 'react';
import { NavLink } from 'react-router-dom';
import FixedMainNavApp from "./fixed_mainnav";

class HeaderApp extends React.Component {
    render() {
        return (
            <React.StrictMode>
                <header id="wrap_header">
                    <div id="wrap_logo">
                        <a href="/">
                            <img id="mainlogo" alt="株式会社logo" />
                        </a>
                    </div>
                    <nav id="wrap_gmenu">
                        <ul>
                            <FixedMainNavApp />
                        </ul>
                    </nav>
                </header>
            </React.StrictMode>
        );
    }
}
export default HeaderApp;

上記のheader.tsxでは、FixedMainNavAppでナビゲーションのリンクを読み込んでいます。

fixed_mainnav.tsx
import React from 'react';
import { Link } from 'react-router-dom';

class FixedMainNavApp extends React.Component {
    render() {
        return (
            <React.StrictMode>
                    <li><Link to="/">ホーム</Link></li>
                    <li><Link to="/outline/">会社概要</Link></li>
                    <li><Link to="/digital/">デジタル事業</Link></li>
                    <li><Link to="/recruit/">リクルート</Link></li>
                    <li><Link to="/sitemap">サイトマップ</Link></li>
            </React.StrictMode>
        );
    }
}
export default FixedMainNavApp;

上記のfixed_mainnav.tsx内でLinkタグをユーザーがクリックすると URL 遷移します。

まとめ

負の遺産は早く捨てたほうが幸せ。
Excelsior!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?