A powerfull UX first IPTV player app for Nintendo Switch. Add M3U URL or XTream playlists (M3U files support coming soon) and watch content anywhere. Just download the lates release and enjoy!
This repository contains the native Kboré homebrew app, including the app UI,
playlist/cache handling, and the native media player pipeline backed by a custom
FFmpeg build with nvtegra patches.
Current player state:
- Native UI, controller input, stream cache, and stream listing are working.
- Custom FFmpeg builds locally and exports the SDK into
external/player-sdk. - The
nvtegraFFmpeg backend is present and detected by the native probe. AVHWDeviceContextcreation succeeds.- Video opens through NVTEGRA hardware decode.
- Audio runs through SDL Audio.
- SD and HD playback are usable with the current SDL texture path.
- FHD playback still needs the future direct native renderer path to avoid CPU frame transfers.
Some low-level toolchain package names, filesystem mount points, build defines, and source files still contain target-specific or legacy identifiers because the homebrew SDK and existing code use those exact names.
Expected project root:
~/kboreRelevant paths:
kbore/
├── Makefile
├── Makefile.switch
├── include/
├── source/
├── scripts/
│ ├── build-ffmpeg-nx.sh
│ ├── check-ffmpeg-nx.sh
│ ├── patch-ffmpeg-nx-switch.sh
│ ├── apply-ffmpeg-nvtegra-0001-manual.sh
│ ├── apply-ffmpeg-nvtegra-0002-manual.sh
│ └── apply-ffmpeg-nvtegra-from-0003-safe.sh
├── patches/
│ └── ffmpeg-nvtegra/
├── external/
│ ├── ffmpeg-nx/
│ ├── mpv-nx/
│ └── player-sdk/
├── romfs/
└── data/
external/ffmpeg-nx and external/mpv-nx are Git submodules that point to the
Kboré forks:
https://github.com/devgsantos/ffmpeg-nx.git
https://github.com/devgsantos/mpv-nx.git
external/player-sdk is generated by the FFmpeg build script.
For a fresh checkout:
git clone --recurse-submodules https://github.com/devgsantos/kbore.git
cd kboreIf the repository was cloned without submodules:
git submodule update --init --recursiveTo refresh the external forks to the commits recorded by this repository:
git submodule sync --recursive
git submodule update --init --recursiveTo intentionally update a submodule to a newer fork commit:
cd external/ffmpeg-nx
git fetch origin
git checkout feat/updated-readme
git pull --ff-only
cd ../..
git add external/ffmpeg-nxRepeat the same flow for external/mpv-nx when needed.
The homebrew build uses devkitPro, devkitA64, libnx, SDL2, curl, mbedTLS, zlib, bzip2, xz/lzma, deko3d, and the static FFmpeg SDK generated from the fork.
Expected toolchain paths:
/opt/devkitpro
/opt/devkitpro/devkitA64
/opt/devkitpro/libnx
/opt/devkitpro/portlibs/switchSet the environment:
export DEVKITPRO=/opt/devkitpro
export DEVKITA64=$DEVKITPRO/devkitA64
export PATH=$DEVKITA64/bin:$PATHAdd the same lines to ~/.bashrc or ~/.zshrc if you want them loaded for each
terminal session.
Verify the compiler:
echo $DEVKITPRO
echo $DEVKITA64
which aarch64-none-elf-gcc
aarch64-none-elf-gcc --versionInstall the base packages:
sudo dkp-pacman -Syu
sudo dkp-pacman -S switch-dev switch-sdl2 switch-sdl2_ttf switch-sdl2_image switch-curl switch-mbedtls switch-zlib switch-bzip2 switch-xz switch-deko3d switch-mesaPackage names can vary by devkitPro repository state. If a package name is not found, search the package database:
dkp-pacman -Ss switch | grep -Ei 'sdl2|curl|mbedtls|zlib|bzip2|xz|lzma|deko3d|mesa'The app also expects host tools commonly available on a Linux development machine:
git
make
bash
pkg-config
python3
grep
sedThe custom FFmpeg fork is the source of truth for the patched nvtegra backend.
Do not copy the full FFmpeg or mpv source trees into the main repository as
regular files.
The main repository should track only the submodule commit references:
git status --short
git submodule statusExpected submodule branches in this checkout:
external/ffmpeg-nx -> feat/updated-readme
external/mpv-nx -> main
If you change FFmpeg patches:
cd external/ffmpeg-nx
git status
git add configure libavcodec libavformat libavutil
git commit -m "Update Kboré NVTEGRA player patches"
git push origin feat/updated-readme
cd ../..
git add external/ffmpeg-nxIf you change mpv integration code:
cd external/mpv-nx
git status
git add .
git commit -m "Update Kboré mpv homebrew integration"
git push origin main
cd ../..
git add external/mpv-nxThe app links against external/player-sdk, generated from external/ffmpeg-nx.
Rebuild it whenever the FFmpeg fork changes or when the SDK is missing.
From the repository root:
rm -rf external/player-sdk
./scripts/build-ffmpeg-nx.sh
./scripts/check-ffmpeg-nx.shThe FFmpeg configure step must include:
--target-os=horizon
--enable-gpl
--enable-nvtegra
--enable-picThe build flags must keep PIE/PIC enabled:
-fPIC
-fPIE
-specs=$DEVKITPRO/libnx/switch.specsRecommended decoder/parser coverage:
--enable-parser=h264,hevc,aac,ac3,mpeg4video,mpegaudio,vp8,vp9
--enable-decoder=h264,hevc,aac,mp3,ac3,eac3,mpeg2video,mpeg4,vp8,vp9Verify the generated SDK:
./scripts/check-ffmpeg-nx.sh
aarch64-none-elf-nm external/player-sdk/lib/libavcodec.a | grep -Ei 'nvtegra|h264_nvtegra|hevc_nvtegra'
aarch64-none-elf-nm external/player-sdk/lib/libavutil.a | grep -Ei 'nvtegra|hwcontext_nvtegra'Expected symbols include:
h264_nvtegra
hevc_nvtegra
vp8_nvtegra
vp9_nvtegra
hwcontext_nvtegra
The patched fork should normally already contain the required NVTEGRA changes. Use the patch scripts only when rebuilding or replaying the patch set from a clean FFmpeg base.
Patch files live in:
patches/ffmpeg-nvtegra/
Confirm they are real patch files:
grep -n "diff --git" patches/ffmpeg-nvtegra/*.patchApply the patch helpers from the repository root:
./scripts/patch-ffmpeg-nx-switch.sh
./scripts/apply-ffmpeg-nvtegra-0001-manual.sh
./scripts/apply-ffmpeg-nvtegra-0002-manual.sh
./scripts/apply-ffmpeg-nvtegra-from-0003-safe.shVerify the FFmpeg tree:
cd external/ffmpeg-nx
grep -R "enable-nvtegra\|nvtegra\|AV_HWDEVICE_TYPE_NVTEGRA" configure libavcodec libavutil -n | head -80
cd ../..Make sure external/player-sdk exists before building the app.
make clean
make switchExpected outputs:
kbore.elf
kbore.nro
kbore.nacp
Makefile.switch should include the generated SDK before portlibs:
PLAYER_SDK := $(APP_ROOT)/external/player-sdk
PLAYER_SDK_INC := $(PLAYER_SDK)/include
PLAYER_SDK_LIB := $(PLAYER_SDK)/lib
export INCLUDE := -I$(PLAYER_SDK_INC) ...
export LIBPATHS := -L$(PLAYER_SDK_LIB) ...The player build currently enables the native hardware path in Makefile.switch.
Place the generated app under the homebrew app folder on the SD card:
sdmc:/switch/kbore/kbore.nro
Suggested SD layout:
sdmc:/switch/kbore/
├── kbore.nro
├── config.json
├── fonts/
│ └── OpenSans-Regular.ttf
├── manifests/
└── cache/
Example config.json:
{
"parserApiBaseUrl": "https://your-parser-api.example",
"apiKey": "your-api-key",
"active": "my-m3u-list",
"pageSize": 20,
"preloadThreshold": 8,
"useUnicodeIcons": false,
"playlists": [
{
"id": "my-m3u-list",
"name": "My M3U List",
"type": "m3u",
"active": true,
"m3u_url": "http://host:port/get.php?username=USER&password=PASS&type=m3u_plus&output=ts",
"epg_url": "http://host:port/xmltv.php?username=USER&password=PASS"
},
{
"id": "my-xtream-list",
"name": "My Xtream List",
"type": "xtream",
"server_url": "http://host:port",
"username": "USER",
"password": "PASS"
}
],
"defaultXtreamUrl": "http://server:port",
"defaultPlaylistUrl": "http://host:port/playlist.m3u"
}defaultXtreamUrl and defaultPlaylistUrl are legacy compatibility fields.
New saved lists should live in playlists, and the selected list is stored in
active / active_playlist_id.
If text renders as boxes, verify the font file exists:
sdmc:/switch/kbore/fonts/OpenSans-Regular.ttf
For playlist names, channel names, and categories with unsupported icon glyphs, normalize or remove the icons before rendering.
Kboré loads M3U and Xtream sources through the configured parser API. The app expects the parser to return a lightweight initial manifest and to load deeper folders through child-page requests instead of returning a full recursive tree on first import.
The important parser endpoints are:
POST /api/nodes/parse-url
POST /api/nodes/xtream/manifest
POST /api/nodes/children
POST /api/xtream/epg/short
The app stores cached data by playlist id so switching saved lists does not force a full re-download every time.
Current cache paths:
sdmc:/switch/kbore/manifests/<playlist-id>_manifest.json
sdmc:/switch/kbore/manifests/<playlist-id>_manifest.json.gz
sdmc:/switch/kbore/cache/<playlist-id>_<provider>_<type>_<category>_page_<n>.json
sdmc:/switch/kbore/cache/<playlist-id>_node_<node-id>_page_<n>.json
sdmc:/switch/kbore/cache/<playlist-id>_epg_<channel-key>.json
Legacy manifest files are still read for compatibility:
sdmc:/switch/kbore/manifests/active-manifest.json
sdmc:/switch/kbore/active-manifest.json
sdmc:/switch/kbore/manifest-<playlist-id>.json
sdmc:/switch/kbore/cache/<playlist-id>_manifest.json
sdmc:/switch/kbore/cache/<playlist-id>_manifest.json.gz
If an existing list keeps loading stale data, remove only that playlist's cached
manifest and page files from manifests/ and cache/, then import it again.
Current video path:
NativeDemuxer
-> custom FFmpeg + nvtegra
-> AVHWDeviceContext nvtegra
-> NativeDecoder::openVideoHardware()
-> AVFrame pix_fmt=nvtegra
-> av_hwframe_transfer_data()
-> YUVFrame
-> SDL renderer
Current audio path:
NativeDemuxer
-> audio packets
-> FFmpeg audio decoder
-> SwrContext
-> S16 stereo 48 kHz
-> SDL_QueueAudio()
The current renderer still transfers decoded frames back through CPU memory. That is acceptable for SD/HD, but FHD needs a direct native render path:
NVTEGRA frame
-> native surface/texture import
-> deko3d renderer
-> present without av_hwframe_transfer_data()
After the FFmpeg SDK is built with NVTEGRA support, the native probe should show:
configs=1
pix_fmt=nvtegra
device=nvtegra
methods=none
usableDeviceConfig=yes
createdDevice=yes
selected=#0
The nvtegra backend can report methods=none. The probe should still accept
the config when device=nvtegra and pix_fmt=nvtegra are present.
Useful log filters:
[KBORE][NVTEGRA]
[KBORE][DEKO3D]
On Switch, runtime diagnostics are appended to:
sdmc:/switch/kbore/kbore.log
Run:
./scripts/patch-ffmpeg-nx-switch.shThe FFmpeg static libraries were likely built without PIE/PIC. Rebuild the SDK with:
--enable-pic
-fPIC
-fPIEFor temporary validation only, test without assembly optimizations:
--disable-asm
--disable-neonThe app is not using an FFmpeg SDK with the NVTEGRA backend. Rebuild and verify:
rm -rf external/player-sdk
./scripts/build-ffmpeg-nx.sh
./scripts/check-ffmpeg-nx.shAlso confirm Makefile.switch uses external/player-sdk before portlibs:
grep -n "PLAYER_SDK\|LIBPATHS\|INCLUDE\|LIBS" Makefile.switchThe probe must accept nvtegra even when methods=none.
Likely audio stream metadata is missing or unexpected. Check the audio fallback helpers:
safeAudioSampleRate()
safeAudioSampleFormat()
safeInputLayout()
makeDefaultLayout()
Also confirm the FFmpeg build includes the required audio decoders and parsers.
Rebuild the FFmpeg SDK:
rm -rf external/player-sdk
./scripts/build-ffmpeg-nx.sh
./scripts/check-ffmpeg-nx.shRebuild Kboré:
make clean
make switchCheck submodules:
git submodule status
git -C external/ffmpeg-nx status --short --branch
git -C external/mpv-nx status --short --branchCheck generated symbols:
aarch64-none-elf-nm external/player-sdk/lib/libavcodec.a | grep -Ei 'nvtegra|h264_nvtegra|hevc_nvtegra'
aarch64-none-elf-nm external/player-sdk/lib/libavutil.a | grep -Ei 'nvtegra|hwcontext_nvtegra'The NVTEGRA FFmpeg backend requires:
--enable-gplIf Kboré is distributed with binaries linked against that FFmpeg build, treat the distribution as GPL-compatible and provide the corresponding source according to the applicable FFmpeg license terms.
Kboré does not provide IPTV lists, channels, movies, series, servers, or streaming content. The app is only a player. All playlists, sources, and content are provided by the user and are the user’s responsibility. Kboré is not affiliated with, sponsored by, or endorsed by any IPTV provider or streaming server.