使いどころ
遺産 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
を再生します。
コード
<!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
イベントが発生できなかったからです。
※この辺り細かく検証できていなくて、分かる方いたらコメントいただけると嬉しいです。
直接loadflash
をaddEventListener
してみましたが、
load
イベント時にplayflash
イベントも同時発生することが分かりました。
そのため、loadflash001
関数を使って、あえて2段編成にしています。
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);
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 を読み込んだ時にしか発生しないからです。
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
のイベントリスナーがキックされます。
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
でナビゲーションのリンクを読み込んでいます。
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!