Skip to content

Instantly share code, notes, and snippets.

View matejdro's full-sized avatar

Matej Drobnič matejdro

View GitHub Profile
@matejdro
matejdro / airplay_to_mpd.md
Last active August 6, 2025 19:12
Streaming from Airplay to MPD

I'm using MPD as a center of my Home's music system (sort of a open source alternative to Sonos).

The biggest problem so far has been that MPD can only play either static music files or HTTP streams. There is no way to stream TO the MPD. For example, I wanted to stream podcasts from my phone (where I already have a good podcasting setup) to my home audio system.

So after some research and a whole lot of trial and error, I came up with this bodge of a setup where I have an Airplay receiver (shairport-sync), whose input eventually gets converted into a HTTP stream that MPD can consume.

The way to do this is to install and enable shairport-sync, set its config file to stream all the audio as raw CD audio into into a unix pipe, and then set it to call start.sh script when the streaming starts and end.sh script then the streaming from the phone stops.

pipe =

PebbleKit Android V2

Old PebbleKit Android depends on app and Pebble app sending broadcasts on every update. Not only is this extremely insecure (every other app on the phone can listen in on those), it is also not working anymore when targeting new SDK versions, so it cannot be used for the new apps (Cobble / Core Devices).

Here is my proposal on how can we create a modern, more convenient and more secure version. It is loosly based on the way WearOS communications work.

Note that "Pebble App" below refers to any Android app that communicates with the watch directly (such as Cobble).

I presume that most apps (both companion apps and the Pebble App) will be written in Kotlin, so following examples are in Kotlin. To support apps written in Java, we can either write the SDK for Java and provide Kotlin wrappers or write it for Kotlin and provide Java wrappers (since Java apps are in minority nowadays, I would go for the latter).

import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.ReceiveChannel
import kotlinx.coroutines.channels.SendChannel
import kotlinx.coroutines.channels.ValueOrClosed
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.consumeAsFlow
import kotlinx.coroutines.flow.emitAll
$thisDirectory = (Get-Location).Path
$notes = Get-ChildItem -Path . -Filter *.md -Recurse -ErrorAction SilentlyContinue -Force
foreach ($note in $notes) {
$relativeFolder = $note.DirectoryName.Replace($thisDirectory, "").Trim().TrimStart("\")
$contents = Get-Content -Path $note.FullName
if ($relativeFolder.Length -eq 0) {
$relativeFolder = "."
}
@matejdro
matejdro / HidableFragment.kt
Last active July 4, 2020 17:22
Android Fragment that holds lifecycle for being hidden
import android.os.Bundle
import androidx.annotation.CallSuper
import androidx.fragment.app.FragmentTransaction
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
/**
* By default [FragmentTransaction.hide] does not trigger any lifecycle events (Fragment is
* still started and resumed even if invisible).
======build/aplite/pebble-app.elf======
Traceback (most recent call last):
File "/mnt/d/Pebble/pebble-tool/pebble.py", line 7, in <module>
pebble_tool.run_tool()
File "/mnt/d/Pebble/pebble-tool/pebble_tool/__init__.py", line 42, in run_tool
args.func(args)
File "/mnt/d/Pebble/pebble-tool/pebble_tool/commands/base.py", line 47, in <lambda>
parser.set_defaults(func=lambda x: cls()(x))
File "/mnt/d/Pebble/pebble-tool/pebble_tool/commands/sdk/project/analyse_size.py", line 40, in __call__
sections = binutils.analyze_elf(path, 'bdt', use_fast_nm=True)
public static void ditherToBlackWhite(LightBitmap bitmap)
{
double[][] separatedColorArray = new double[bitmap.getWidth()][bitmap.getHeight()];
for (int y = 0; y < bitmap.getHeight(); y++)
{
for (int x = 0; x < bitmap.getWidth(); x++)
{
separatedColorArray[x][y] = getGrayscaleColor(bitmap.getPixel(x, y));
}
IgnoredMobs:
- HORSE_DARK_BROWN_WHITE_DOTS
- HORSE_BROWN_WHITEFIELD
- HORSE_BLACK_WHITEFIELD
- VILLAGER
- IRON_GOLEM
- HORSE_WHITE_BLACK_DOTS
- HORSE_CREAMY_WHITEFIELD
- SQUID
- HORSE_CREAMY_WHITE_DOTS
Announcements:
Testing:
- if group is Guardian:
- message: Hello mighty admin
- if group is Novice:
- Pick:
- message: Hi
- message: Hello
- message: Good Day
@matejdro
matejdro / gist:8423662
Last active January 3, 2016 06:39 — forked from riddle/gist:8423562
Debug: false
Messages:
NoPermission: '&cNo permission!'
ConfigurationReloaded: '&aConfiguration reloaded successfully!'
AnnouncementPrefix: '&6'
Groups:
Inherited:
- Novice
- Flatcorian
- Nomad