Updates build_whitelabel to regenerate album art for android auto app
@@ -15,6 +15,7 @@
|
||||
}
|
||||
},
|
||||
"postCreateCommand": "flutter pub get && python3 -m venv .venv && . .venv/bin/activate && pip install --upgrade pip && pip install -r requirements.txt && curl -fsSL https://claude.ai/install.sh | bash",
|
||||
"postStartCommand": ".venv/bin/activate",
|
||||
"containerEnv": {
|
||||
"ANTHROPIC_BASE_URL": "http://192.168.1.108:1234",
|
||||
"ANTHROPIC_API_KEY": "lmstudio",
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"/>
|
||||
|
||||
<application
|
||||
android:label="Fire FM"
|
||||
android:label="KMountain Flower Radio"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
|
||||
@@ -6,6 +6,8 @@ import android.app.NotificationManager
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.support.v4.media.MediaBrowserCompat
|
||||
@@ -38,7 +40,7 @@ class KryzAutoMediaBrowserService : MediaBrowserServiceCompat() {
|
||||
private const val TAG = "KryzAutoService"
|
||||
private const val MEDIA_ID_ROOT = "kryz_root"
|
||||
private const val MEDIA_ID_LIVE = "kryz_live_stream"
|
||||
private const val STREAM_URL = "http://www.stream.org/stream"
|
||||
private const val STREAM_URL = "https://libretime.westerntechnologies.duckdns.org/main"
|
||||
private const val NOTIFICATION_CHANNEL_ID = "com.kryzgoflutter.auto.channel"
|
||||
private const val NOTIFICATION_ID = 1001
|
||||
}
|
||||
@@ -51,6 +53,14 @@ class KryzAutoMediaBrowserService : MediaBrowserServiceCompat() {
|
||||
private var pendingStopRequest: Boolean = false
|
||||
private var reconnectScheduled: Boolean = false
|
||||
|
||||
/**
|
||||
* Album-art bitmap loaded from the drawable resource.
|
||||
* Embedded directly into MediaMetadata so Android Auto cannot serve a stale URI cache.
|
||||
*/
|
||||
private val albumArtBitmap: Bitmap by lazy {
|
||||
BitmapFactory.decodeResource(resources, R.drawable.kryz_auto_art)
|
||||
}
|
||||
|
||||
private val mediaControllerCallback = object : MediaControllerCompat.Callback() {
|
||||
override fun onPlaybackStateChanged(state: PlaybackStateCompat?) {
|
||||
applyPlaybackStateFromFlutter(state)
|
||||
@@ -117,6 +127,20 @@ class KryzAutoMediaBrowserService : MediaBrowserServiceCompat() {
|
||||
dispatchPlayCommand()
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an android.resource Uri for the Auto album-art drawable.
|
||||
* Uses R.drawable.kryz_auto_art so the resource shrinker sees the reference
|
||||
* and keeps the image in release APKs.
|
||||
*/
|
||||
private fun getAutoArtUri(): Uri {
|
||||
return Uri.Builder()
|
||||
.scheme("android.resource")
|
||||
.authority(packageName)
|
||||
.appendPath(resources.getResourceTypeName(R.drawable.kryz_auto_art))
|
||||
.appendPath(resources.getResourceEntryName(R.drawable.kryz_auto_art))
|
||||
.build()
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
Log.d(TAG, "onCreate()")
|
||||
@@ -163,7 +187,7 @@ class KryzAutoMediaBrowserService : MediaBrowserServiceCompat() {
|
||||
) {
|
||||
Log.d(TAG, "onLoadChildren($parentId)")
|
||||
if (parentId == MEDIA_ID_ROOT) {
|
||||
val iconUri = Uri.parse("android.resource://${packageName}/mipmap/ic_launcher")
|
||||
val iconUri = getAutoArtUri()
|
||||
val description = MediaDescriptionCompat.Builder()
|
||||
.setMediaId(MEDIA_ID_LIVE)
|
||||
.setTitle("KMTN Live Stream")
|
||||
@@ -330,7 +354,7 @@ class KryzAutoMediaBrowserService : MediaBrowserServiceCompat() {
|
||||
) {
|
||||
if (!::mediaSession.isInitialized) return
|
||||
|
||||
val iconUri = Uri.parse("android.resource://${packageName}/drawable/kryz_auto_art")
|
||||
val iconUri = getAutoArtUri()
|
||||
mediaSession.setMetadata(
|
||||
MediaMetadataCompat.Builder()
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, title ?: "KMTN Live Stream")
|
||||
@@ -338,8 +362,8 @@ class KryzAutoMediaBrowserService : MediaBrowserServiceCompat() {
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, album ?: "Live")
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, MEDIA_ID_LIVE)
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_MEDIA_URI, mediaUri ?: STREAM_URL)
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, iconUri.toString())
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI, iconUri.toString())
|
||||
.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, albumArtBitmap)
|
||||
.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, albumArtBitmap)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
@@ -428,15 +452,14 @@ class KryzAutoMediaBrowserService : MediaBrowserServiceCompat() {
|
||||
}
|
||||
|
||||
private fun buildLiveStreamMetadata(): MediaMetadataCompat {
|
||||
val iconUri = Uri.parse("android.resource://${packageName}/mipmap/ic_launcher")
|
||||
return MediaMetadataCompat.Builder()
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, "KMTN Live Stream")
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, "KMountain Radio")
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, "Live")
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, MEDIA_ID_LIVE)
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_MEDIA_URI, STREAM_URL)
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, iconUri.toString())
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI, iconUri.toString())
|
||||
.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, albumArtBitmap)
|
||||
.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, albumArtBitmap)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 811 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 31 KiB |
@@ -5,7 +5,7 @@
|
||||
#E36A18 matches AppThemeBusinessObject.lightColors.primarySeed (brand orange).
|
||||
-->
|
||||
<resources>
|
||||
<color name="kryz_primary">#000000</color>
|
||||
<color name="kryz_primary_dark">#0d0d0d</color>
|
||||
<color name="kryz_accent">#ff7a33</color>
|
||||
<color name="kryz_primary">#1a3a5c</color>
|
||||
<color name="kryz_primary_dark">#0e2440</color>
|
||||
<color name="kryz_accent">#e87a2e</color>
|
||||
</resources>
|
||||
|
||||
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 422 KiB |
@@ -1,171 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="1000"
|
||||
height="1000"
|
||||
viewBox="0 0 1000 1000"
|
||||
version="1.1"
|
||||
id="svg7"
|
||||
sodipodi:docname="icon_2.svg"
|
||||
inkscape:version="1.4.3 (fcd0343856, 2026-01-01)"
|
||||
inkscape:export-filename="app_icon.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs7" />
|
||||
<sodipodi:namedview
|
||||
id="namedview7"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="0.68550502"
|
||||
inkscape:cx="485.04386"
|
||||
inkscape:cy="511.30187"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="962"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg7" />
|
||||
<!-- Background -->
|
||||
<rect
|
||||
x="-2"
|
||||
y="-2"
|
||||
width="1002"
|
||||
height="1002"
|
||||
fill="#0b2e59"
|
||||
id="rect1"
|
||||
style="stroke-width:1.77131" />
|
||||
<!-- Accent bands -->
|
||||
<rect
|
||||
x="19.82901"
|
||||
y="426.9852"
|
||||
width="960.34198"
|
||||
height="79.542099"
|
||||
fill="#f58220"
|
||||
opacity="0.9"
|
||||
id="rect2"
|
||||
style="stroke-width:1.54503" />
|
||||
<rect
|
||||
x="19.82901"
|
||||
y="506.52728"
|
||||
width="960.34198"
|
||||
height="39.771049"
|
||||
fill="#ffb347"
|
||||
opacity="0.9"
|
||||
id="rect3"
|
||||
style="stroke-width:1.54503" />
|
||||
<!-- Subtle inner border -->
|
||||
<rect
|
||||
x="21.657974"
|
||||
y="21.657974"
|
||||
width="956.68402"
|
||||
height="956.68408"
|
||||
fill="none"
|
||||
stroke="#f58220"
|
||||
stroke-width="7.31594"
|
||||
id="rect4" />
|
||||
<!-- Letters group -->
|
||||
<g
|
||||
font-family="system-ui, '-apple-system', BlinkMacSystemFont, 'Segoe UI', sans-serif"
|
||||
font-weight="800"
|
||||
text-anchor="middle"
|
||||
dominant-baseline="middle"
|
||||
id="g7"
|
||||
transform="matrix(1.4298147,0,0,1.4298147,-109.59816,77.582873)">
|
||||
<!-- K -->
|
||||
<text
|
||||
x="150"
|
||||
y="190"
|
||||
font-size="160px"
|
||||
fill="#f58220"
|
||||
id="text4">K</text>
|
||||
<!-- R -->
|
||||
<text
|
||||
x="320"
|
||||
y="190"
|
||||
font-size="160px"
|
||||
fill="#ffffff"
|
||||
id="text5">R</text>
|
||||
<!-- Y -->
|
||||
<text
|
||||
x="490"
|
||||
y="190"
|
||||
font-size="160px"
|
||||
fill="#f58220"
|
||||
id="text6">Y</text>
|
||||
<!-- G in a blue block -->
|
||||
<rect
|
||||
x="580"
|
||||
y="80"
|
||||
width="170"
|
||||
height="220"
|
||||
rx="18"
|
||||
ry="18"
|
||||
fill="#123b73"
|
||||
id="rect6" />
|
||||
<text
|
||||
x="665"
|
||||
y="190"
|
||||
font-size="160px"
|
||||
fill="#ffb347"
|
||||
id="text7">Z</text>
|
||||
</g>
|
||||
<!-- Small underline accent under the word -->
|
||||
<rect
|
||||
x="19.82901"
|
||||
y="415.05389"
|
||||
width="960.34204"
|
||||
height="11.931314"
|
||||
fill="#ffb347"
|
||||
opacity="0.85"
|
||||
id="rect7"
|
||||
style="stroke-width:2.08331" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-weight:bold;font-size:100.118px;line-height:0;font-family:'Noto Sans Mono CJK KR';-inkscape-font-specification:'Noto Sans Mono CJK KR Bold';letter-spacing:0px;writing-mode:lr-tb;direction:ltr;fill:#ffffff;stroke:#000000;stroke-width:1.65284"
|
||||
x="22.209259"
|
||||
y="501.82516"
|
||||
id="text8"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan7"
|
||||
x="22.209259"
|
||||
y="501.82516"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:100.118px;font-family:C059;-inkscape-font-specification:'C059, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke:none;stroke-width:1.65284">98.5 LPFM</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:italic;font-variant:normal;font-weight:500;font-stretch:normal;font-size:169.76px;line-height:0.8;font-family:Z003;-inkscape-font-specification:'Z003, Medium Italic';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;writing-mode:lr-tb;direction:ltr;fill:#ffffff;stroke:none;stroke-width:2.80256"
|
||||
x="25.539324"
|
||||
y="669.90143"
|
||||
id="text10"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan10"
|
||||
x="25.539324"
|
||||
y="669.90143"
|
||||
style="line-height:0.8;stroke-width:2.80256">Mariposa</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="25.539324"
|
||||
y="805.70941"
|
||||
id="tspan11"
|
||||
style="line-height:0.8;stroke-width:2.80256">Community</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="25.539324"
|
||||
y="941.5174"
|
||||
style="line-height:0.8;stroke-width:2.80256"
|
||||
id="tspan13">Radio</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:italic;font-variant:normal;font-weight:500;font-stretch:normal;font-size:348.901px;line-height:0.8;font-family:Z003;-inkscape-font-specification:'Z003, Medium Italic';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;writing-mode:lr-tb;direction:ltr;fill:#ffffff;stroke:none;stroke-width:5.76"
|
||||
x="867.28961"
|
||||
y="762.51642"
|
||||
id="text12"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan12" /></text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 5.3 KiB |
@@ -256,6 +256,75 @@ def generate_icon(theme: dict, dry_run: bool = False) -> None:
|
||||
print(" [icon] Install cairosvg (`pip install cairosvg`) or rsvg-convert")
|
||||
|
||||
|
||||
def generate_android_auto_art(theme: dict, dry_run: bool = False) -> None:
|
||||
"""Decode embedded hero_icon (JPEG, PNG, or SVG) and write kryz_auto_art.png."""
|
||||
embedded = theme["images_embedded"]
|
||||
hero_data = embedded["hero_icon"]["data"]
|
||||
hero_content_type = embedded["hero_icon"].get("content_type", "")
|
||||
print(" [android-auto-art] Using 'hero_icon' from images_embedded")
|
||||
|
||||
raw_bytes = base64.b64decode(hero_data)
|
||||
icon_path = os.path.join(PROJECT_ROOT, "android", "app", "src", "main", "res", "drawable-nodpi", "kryz_auto_art.png")
|
||||
|
||||
if dry_run:
|
||||
print(f" [android-auto-art] Would generate PNG from embedded {hero_content_type or 'image'} at android/app/src/main/res/drawable-nodpi/kryz_auto_art.png")
|
||||
return
|
||||
|
||||
# --- JPEG / PNG: convert to PNG via Pillow, or write as-is ---
|
||||
if hero_content_type in ("image/jpeg", "image/png"):
|
||||
try:
|
||||
from PIL import Image
|
||||
import io
|
||||
img = Image.open(io.BytesIO(raw_bytes)).convert("RGBA")
|
||||
img = img.resize((1024, 1024), Image.LANCZOS)
|
||||
img.save(icon_path, "PNG")
|
||||
print(f" [android-auto-art] Converted {hero_content_type} → PNG (Pillow, 1024×1024)")
|
||||
return
|
||||
except ImportError:
|
||||
pass
|
||||
except Exception as e:
|
||||
print(f" [android-auto-art] Pillow conversion failed: {e} — writing raw bytes")
|
||||
|
||||
# Fallback: write raw bytes directly
|
||||
if hero_content_type == "image/jpeg":
|
||||
fallback = os.path.join(PROJECT_ROOT, "android", "app", "src", "main", "res", "drawable-nodpi", "kryz_auto_art.jpg")
|
||||
else:
|
||||
fallback = icon_path
|
||||
with open(fallback, "wb") as f:
|
||||
f.write(raw_bytes)
|
||||
print(f" [android-auto-art] Wrote embedded {hero_content_type} (no Pillow — using raw bytes)")
|
||||
if fallback != icon_path:
|
||||
os.replace(fallback, icon_path)
|
||||
return
|
||||
|
||||
# --- SVG: render to PNG via cairosvg or rsvg-convert ---
|
||||
try:
|
||||
import cairosvg
|
||||
cairosvg.svg2png(bytestring=raw_bytes, write_to=icon_path, output_width=1024, output_height=1024)
|
||||
print(" [android-auto-art] Rendered SVG → PNG (cairosvg)")
|
||||
return
|
||||
except ImportError:
|
||||
pass
|
||||
except Exception as e:
|
||||
print(f" [android-auto-art] cairosvg failed: {e} — trying rsvg-convert")
|
||||
|
||||
try:
|
||||
import tempfile
|
||||
with tempfile.NamedTemporaryFile(suffix=".svg", delete=False) as tmp:
|
||||
tmp.write(raw_bytes)
|
||||
tmp_path = tmp.name
|
||||
subprocess.run(
|
||||
["rsvg-convert", "-w", "1024", "-h", "1024", "-o", icon_path, tmp_path],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
)
|
||||
os.unlink(tmp_path)
|
||||
print(" [android-auto-art] Rendered SVG → PNG (rsvg-convert)")
|
||||
except Exception as e:
|
||||
print(f" [android-auto-art] WARNING: Could not render icon: {e}")
|
||||
print(" [android-auto-art] Install cairosvg (`pip install cairosvg`) or rsvg-convert")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Native file patching
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -467,6 +536,10 @@ def main():
|
||||
print()
|
||||
generate_icon(theme, dry_run=args.dry_run)
|
||||
|
||||
# Generate Android Auto album art
|
||||
print()
|
||||
generate_android_auto_art(theme, dry_run=args.dry_run)
|
||||
|
||||
# Patch native files
|
||||
print()
|
||||
patch_android_manifest(theme, dry_run=args.dry_run)
|
||||
|
||||
|
Before Width: | Height: | Size: 158 KiB After Width: | Height: | Size: 420 KiB |
|
Before Width: | Height: | Size: 625 B After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 9.2 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 68 B After Width: | Height: | Size: 68 B |
|
Before Width: | Height: | Size: 68 B After Width: | Height: | Size: 68 B |
|
Before Width: | Height: | Size: 68 B After Width: | Height: | Size: 68 B |
@@ -7,7 +7,7 @@
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Fire FM</string>
|
||||
<string>KMountain Flower Radio</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
@@ -15,7 +15,7 @@
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>Fire FM</string>
|
||||
<string>KMountain Flower Radio</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
|
||||