Safariのようにタブ単位でミュートをかけたい!と検索していたら見つかったので書き記しておきます。
2018年2月15日更新
High Sierraにて-[WKWebView _setPageMute:]
メソッドが取り込まれましたので、新たに記事をかきました。
SafariのようにWKWebView単位でミュートをかける 最新版
この方法にはWebKitへのハックが含まれており、WebKitのアップデートにより利用不可能となる可能性があります。
また、WebKitの次のアップデートでより簡単な方法が使えるようになる可能性があります。
プロジェクトファイル一式
文章を読むの面倒な方はこれをみてください。
https://github.com/masakih/WKWebKitMuteExt
ハックする場所
@implementation WKWebView {
...
- (void)_setPageMuted:(_WKMediaMutedState)mutedState
{
WebCore::MediaProducer::MutedStateFlags coreState = WebCore::MediaProducer::NoneMuted;
if (mutedState & _WKMediaAudioMuted)
coreState |= WebCore::MediaProducer::AudioIsMuted;
if (mutedState & _WKMediaCaptureDevicesMuted)
coreState |= WebCore::MediaProducer::CaptureDevicesAreMuted;
_page->setMuted(coreState);
}
...
}
@interface WKWebView () WK_WEB_VIEW_PROTOCOLS {
...
RefPtr<WebKit::WebPageProxy> _page;
...
}
void WebPageProxy::setMuted(WebCore::MediaProducer::MutedStateFlags state)
enum MutedState {
NoneMuted = 0,
AudioIsMuted = 1 << 0,
CaptureDevicesAreMuted = 1 << 1,
};
typedef unsigned MutedStateFlags;
-[WKWebView _setPageMuted:]
はgithubへは2017年4月13日にマージされています。
この変更はまだmacOSに反映されていません。(2017年5月22日現在)
これと同じことをカテゴリなどで実装するのが今回の趣旨です。
方法
簡単に言えば、 WKWebView
の_page
プロパティのインスタンス関数 WebPageProxy::setMuted()
を呼べばいいだけです。
ただ、_page
がC++のオブジェクトのためちょっと面倒臭いことが必要です。
Power Tool
twitterで @hetima さんより強力なツールを教えていただきました。 正直何をやっているかよくわかってません。
機能は関数などのシンボルをオブジェクトファイルなどから検索しアドレスを取得するなどです。
主機能はアドレスを差し替えてライブラリの機能を上書きする、もののようです。
今回はシンボル名からアドレスを取り出すだけに使っています。
シンボルを調べる
githubに上げているものは実行時に調べていますが通常は
$ nm /path/to/lib | grep [name of class] | grep [name of method]
あたりで調べられます。
最後のカラムがシンボル名です。
今回見つかったシンボルは
__ZN6WebKit12WebPageProxy8setMutedEj
です
なんとなくWebKit::WebPageProxy::setMuted
が見えますね。
シンボルからアドレスを取り出す
上の方法でWebPageProxy::setMuted()
のシンボルが__ZN6WebKit12WebPageProxy8setMutedEj
とわかりましたので、これからアドレスを取り出します。
@hetima さんのHTSymbolHookを使います。
typedef void (*SetMuteFunc)(void*, NSInteger);
NSString *symName = @"__ZN6WebKit12WebPageProxy8setMutedEj";
SetMuteFunc getSetMuteFunc() {
HTSymbolHook *hook = [HTSymbolHook symbolHookWithImageNameSuffix:@"/WebKit"];
return (SetMuteFunc)[hook symbolPtrWithSymbolName:symName];
}
これでインスタンス関数のアドレスが取得できます。
WKWebViewから_pageプロパティを取り出す
これは普通にobjc/runtime.h
の各関数を使います。
static const char *pagePropertyName = "_page";
void *getPage(id instance) {
Class classObj = object_getClass(instance);
Ivar ivar = class_getInstanceVariable(classObj, pagePropertyName);
void *ptr = object_getIvar(instance, ivar);
return ptr;
}
注意しておきたいのはARCが有効だとC++のインスタンスに対し objc_retainAutoreleasedReturnValue()
が使われてしまうためクラッシュしてしまうことです。
別ファイルに分けてARCを無効にしておきましょう。
ミュートする
必要な値は全て揃いましたので、これらを使ってWKWebViewをミュートします。
typedef void (*SetMuteFunc)(void*, NSInteger);
WKWebView *webView;
void *page = getPage(webView);
SetMuteFunc setMute = getSetMuteFunc();
setMute(page, AudioIsMuted);
// setMute(page, NoneMuted);
githubにあげたプロジェクトの概要
上にあげたプロジェクトでは WKWebView
にmute
プロパティを設けて簡単に設定できるようにしています。
また、WebPageProxy::setMuted()
のシンボルを実行時に検索するようにし、可能な限りWebKitのアップデートに対応できるようにしています。
おわりに
C++の中身は全くわかりませんでしたが、 @hetima さんの数々の助言でやりたいことが実現しました。
本当にありがとうございます。
ただ、使おうと思っていたプロジェクトで他の制約からWKWebViewが使えないということが発覚し僕の中では残念な結果で終わってしまいました。
そのまま捨て去るのが癪に触るのでここに記事をあげさせていただきました。
お目汚しを...
あと、上に書いた通り、最近WKWebView
にミュート用メソッドが追加されていますので、近いうちにmacOSにも入って簡単にハックできるようになるかもしれません。
使いたい人は注視しておくといいかも?