ヘッドフォンを置いたときに音楽を止めるデバイスを作る

トイレとか食事の時にヘッドフォンを外してYoutubeやらMusicやらを止めて...ってやるのがめんどくさかったのでヘッドフォンを置いた時に勝手に止めるようなデバイスを作りました。
デバイスと言いつつほとんどソフトウェア側の実装です。
また、MacOSでしか動きません。
完成したコードは[ここ](https://github.com/yugosuz/stop_sound)からダウンロードできます

# 用意するもの
- ヘッドフォン
- パソコン（MacOS以外は想定してないです）
- Arduino（Arduino IDEで書き込めれば何でも良い）
- [リードスイッチ](https://www.sparkfun.com/products/8642)（磁石に反応するセンサ）
- 磁石

リードスイッチじゃなくても大丈夫です。スイッチとか

# 組み立て
Arduinoとリードスイッチを以下のように接続します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2626540/aa5ce295-fe02-d144-496c-a4aa812a9a81.png)
https://learn.sparkfun.com/tutorials/reed-switch-hookup-guide?_ga=2.265836815.1235452650.1682338515-855519520.1682338515 より引用

デバイスは以下のようなものを作成しました。
![Screenshot 2023-04-24 at 21.24.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2626540/7e57fe56-ffd2-6d31-3d30-8f6e01918539.png)
スタンドの上部にリードスイッチをつけ、スタンドとヘッドフォンが接触する場所に磁石をつけます。こうすることでリードスイッチが反応するので置いたかどうか判断できます。
# コード
Arduinoに書き込むコードです。
```java
const int REED = 2;
bool is_placed = false;
void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  pinMode(REED, INPUT_PULLUP);
}

void loop() {
  // put your main code here, to run repeatedly:
  int prox = digitalRead(REED);
  if (prox == 0) {
    if (is_placed == false) {
      Serial.println("detected");
      is_placed = true;
    }
  } else {
    if (is_placed == true) {
      is_placed = false;
    }
  }
  delay(500);
}
```
やってることはREEDピンからの値が0になったら`"detected"`と出力しているだけです。何度も`"detected"`を送るのが嫌なので前フレームで出力した時は出力しないようにしています。

ブラウザやアプリを直接操作するためにAppleScriptを使います。
AppleScriptのコードです。
```
to muteSafari()
	if application "Safari" is running then
		tell application "Safari"
			try
				set windowcount to number of windows
                --ウィンドウ数でループ
				repeat with x from 1 to windowcount
                    --タブ数でループ
					set tabcount to number of tabs in window x
					repeat with y from 1 to tabcount
						set tabName to name of tab y of window x
						set tabURL to URL of tab y of window x
						set temp to "youtube"
                        --タブのurlにyoutubeという文字が含まれていたら
						if (offset in tabURL of temp) > 0 then
                            --再生中か否かを取得
							set isPaused to (do JavaScript "document.querySelector('#movie_player video').paused" in tab y of window x)
                            --再生中なら再生ボタンをクリックし、停止
							if isPaused = false then
								set theScript to "document.getElementsByClassName('ytp-play-button')[0].click()"
								do JavaScript theScript in tab y of window x
							end if
						end if
					end repeat
				end repeat
			end try
		end tell
	end if
end muteSafari
to muteMusic()
    --Musicが再生中なら
	if application "Music" is running then
        --音楽を止める
		tell application "Music" to pause
	end if
end muteMusic
to muteChrome()
	if application "Google Chrome" is running then
		tell application "Google Chrome"
			try
				set windowcount to number of windows
				
				repeat with z from 1 to windowcount
					set tabcount to number of tabs in window z
					repeat with w from 1 to tabcount
						--Get Tab Name & URL
						set tabName to title of tab w of window z
						set tabURL to URL of tab w of window z
						set temp to "youtube"
						if (offset in tabURL of temp) > 0 then
							set isPaused to (execute of tab w of window z javascript "document.querySelector('#movie_player video').paused")
							if isPaused = false then
								set theScript to "document.getElementsByClassName('ytp-play-button')[0].click()"
								execute of tab w of window z javascript theScript
							end if
						end if
					end repeat
				end repeat
			end try
		end tell
	end if
end muteChrome
muteSafari()
muteMusic()
muteChrome()
```
とても見辛いのですが、やっていることは
- SafariのYoutubeを止める
- ChromeのYoutubeを止める
- Musicを止める
の三つです。関数はそれぞれ`muteSafari`, `muteChrome`, `muteMusic`です。

Arduinoからの出力を受け取るPythonコードを書きます。
```python
import serial
import atexit
import subprocess
import sys
import os

ser = serial.Serial("シリアルポート名", 115200)

def all_done():
    # 終了時の処理
    print("\nDone!")
    ser.close()

def main():
    atexit.register(all_done)
    try:
        while(True):
            line = ser.readline().strip().decode('utf-8')
            # 何かしらのトリガーがあったらスクリプトを実行する。
            # 今回はシリアルモニタから"detected"という文字列が送られてきたら実行する。
            if line == "detected":
                subprocess.run(["osascript", "/path/to/stopyoutube.scpt"])
                print("stopped!")

    except KeyboardInterrupt:
        sys.exit(0)


if __name__ == "__main__":
    main()
```
シリアルモニタを読んで、`"detected"`を検知したら先ほどのAppleScriptを実行しているだけです。

# 動かす
`$ python runapplescript.py`

ブラウザで動かしていたYoutube、Musicが止まれば成功です


ありがとうございました。


# 参考文献
[Reed Switch Hookup Guide](https://learn.sparkfun.com/tutorials/reed-switch-hookup-guide?_ga=2.265836815.1235452650.1682338515-855519520.1682338515)
[AppleScriptでアプリケーションのウインドウ数を取得](https://qiita.com/takeohman/items/4c3e95df7aeac78dc9d0)