<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: jemontenis</title>
    <description>The latest articles on DEV Community by jemontenis (@jemontenis).</description>
    <link>https://dev.to/jemontenis</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3883634%2F90f4c005-1996-4f0b-aaa1-56419efbb6fb.png</url>
      <title>DEV Community: jemontenis</title>
      <link>https://dev.to/jemontenis</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jemontenis"/>
    <language>en</language>
    <item>
      <title>I built a lightweight Windows internet radio player with C#, WebView2</title>
      <dc:creator>jemontenis</dc:creator>
      <pubDate>Fri, 17 Apr 2026 04:51:39 +0000</pubDate>
      <link>https://dev.to/jemontenis/i-built-a-lightweight-windows-internet-radio-player-with-c-webview2-4p0b</link>
      <guid>https://dev.to/jemontenis/i-built-a-lightweight-windows-internet-radio-player-with-c-webview2-4p0b</guid>
      <description>&lt;p&gt;I built a small Windows app called &lt;strong&gt;Tunexori&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fym2i9oeuebb34c4m9m1c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fym2i9oeuebb34c4m9m1c.png" alt=" " width="800" height="510"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is a lightweight internet radio player for direct stream URLs. The idea is simple: you add your own radio streams,&lt;br&gt;
  press play, and the app keeps running quietly in the system tray.&lt;/p&gt;

&lt;p&gt;GitHub Releases page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/jemontenis/Tunexori/releases" rel="noopener noreferrer"&gt;https://github.com/jemontenis/Tunexori/releases&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why I made it&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I wanted a simple radio player for Windows without extra accounts, catalogs, ads, or complicated menus.&lt;/p&gt;

&lt;p&gt;Most of the time I only need a few direct radio stream links. I do not need a full media center. I just want to open&lt;br&gt;
  the app, choose a stream, and keep it playing while I work.&lt;/p&gt;

&lt;p&gt;So I made Tunexori around that workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;add your own stream URLs&lt;/li&gt;
&lt;li&gt;switch between them quickly&lt;/li&gt;
&lt;li&gt;minimize the app to the tray&lt;/li&gt;
&lt;li&gt;keep playback running in the background&lt;/li&gt;
&lt;li&gt;use a sleep timer or alarm when needed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What the app does&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Tunexori supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;custom internet radio stream URLs&lt;/li&gt;
&lt;li&gt;play, pause, previous, and next controls&lt;/li&gt;
&lt;li&gt;volume control&lt;/li&gt;
&lt;li&gt;Windows system tray menu&lt;/li&gt;
&lt;li&gt;minimizing to tray without stopping playback&lt;/li&gt;
&lt;li&gt;audio level visualization&lt;/li&gt;
&lt;li&gt;alarm for starting the current station at a selected time&lt;/li&gt;
&lt;li&gt;sleep timer for pausing playback later&lt;/li&gt;
&lt;li&gt;track history for streams that provide ICY metadata&lt;/li&gt;
&lt;li&gt;export of track history to a text file&lt;/li&gt;
&lt;li&gt;update checks through GitHub Releases&lt;/li&gt;
&lt;li&gt;manual language selection: automatic, English, or Russian&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The app starts with no predefined stations. The user adds their own streams manually.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tech stack&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Tunexori is built with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;C#&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;.NET 9&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Windows Forms&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Microsoft Edge WebView2&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HTML / CSS / JavaScript&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NAudio&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PowerShell&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitHub Releases&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The main desktop shell is WinForms. The interface inside the app is rendered with WebView2. Audio playback is handled&lt;br&gt;
  with NAudio.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why WinForms and WebView2?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I wanted the app to feel like a real Windows desktop application, but I also wanted a flexible UI.&lt;/p&gt;

&lt;p&gt;WinForms gives me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;native window&lt;/li&gt;
&lt;li&gt;tray icon&lt;/li&gt;
&lt;li&gt;context menus&lt;/li&gt;
&lt;li&gt;file dialogs&lt;/li&gt;
&lt;li&gt;simple installer UI&lt;/li&gt;
&lt;li&gt;easy Windows integration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;WebView2 gives me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;modern layout&lt;/li&gt;
&lt;li&gt;CSS styling&lt;/li&gt;
&lt;li&gt;easier UI iteration&lt;/li&gt;
&lt;li&gt;a cleaner visual design than classic WinForms controls&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the app uses WinForms as the native host and WebView2 for the main interface.&lt;/p&gt;

&lt;p&gt;The result is still a normal Windows app, but the main UI can be designed with HTML, CSS, and JavaScript.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Audio playback&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For playback I used &lt;strong&gt;NAudio&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The app accepts direct HTTP or HTTPS stream URLs. When the user starts a station, the app opens the stream and sends&lt;br&gt;
  the audio to the system output.&lt;/p&gt;

&lt;p&gt;I also added a simple connection watchdog. If the stream stops sending data or fails, the app can show a status and&lt;br&gt;
  try to reconnect.&lt;/p&gt;

&lt;p&gt;This is useful because internet radio streams are not always stable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Track metadata&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Some radio streams send ICY metadata. This can include the current track title.&lt;/p&gt;

&lt;p&gt;Tunexori reads that metadata when it is available and shows the current track in the interface. It also keeps a local&lt;br&gt;
  track history.&lt;/p&gt;

&lt;p&gt;The history can be exported as a text file.&lt;/p&gt;

&lt;p&gt;Not every stream provides metadata, so this feature depends on the station.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Settings&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Settings are stored locally in JSON.&lt;/p&gt;

&lt;p&gt;On Windows, the settings file is stored here:&lt;/p&gt;

&lt;p&gt;%APPDATA%\Tunexori\settings.json&lt;/p&gt;

&lt;p&gt;The WebView2 profile is stored here:&lt;/p&gt;

&lt;p&gt;%LOCALAPPDATA%\Tunexori\WebView2&lt;/p&gt;

&lt;p&gt;The app saves things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;station list&lt;/li&gt;
&lt;li&gt;selected station&lt;/li&gt;
&lt;li&gt;volume&lt;/li&gt;
&lt;li&gt;alarm settings&lt;/li&gt;
&lt;li&gt;startup settings&lt;/li&gt;
&lt;li&gt;language preference&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;System tray behavior&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One of the important parts of the app is tray behavior.&lt;/p&gt;

&lt;p&gt;When the user closes or minimizes the window, the radio can continue playing. The tray icon gives quick access to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;play / pause&lt;/li&gt;
&lt;li&gt;previous station&lt;/li&gt;
&lt;li&gt;next station&lt;/li&gt;
&lt;li&gt;station list&lt;/li&gt;
&lt;li&gt;stream settings&lt;/li&gt;
&lt;li&gt;show or hide the window&lt;/li&gt;
&lt;li&gt;exit&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes the app useful as a background player instead of something that always needs to stay open on the desktop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub Releases for update checks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I did not want to build a complex update server.&lt;/p&gt;

&lt;p&gt;Instead, Tunexori checks the latest GitHub Release.&lt;/p&gt;

&lt;p&gt;The app calls the GitHub API endpoint:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://api.github.com/repos/jemontenis/Tunexori/releases/latest" rel="noopener noreferrer"&gt;https://api.github.com/repos/jemontenis/Tunexori/releases/latest&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then it compares the latest release tag with the current app version.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;Installed version: 1.0.1&lt;br&gt;
  Latest GitHub tag: v1.0.2&lt;/p&gt;

&lt;p&gt;If the GitHub version is newer, the app shows that a new version is available.&lt;/p&gt;

&lt;p&gt;I kept the update flow manual for now. If an update is found, the app shows a button to open the download page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/jemontenis/Tunexori/releases/latest" rel="noopener noreferrer"&gt;https://github.com/jemontenis/Tunexori/releases/latest&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is simpler and safer than automatically downloading and running an installer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Installer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The app has a Windows installer.&lt;/p&gt;

&lt;p&gt;The current public installer is an app-only installer. It installs Tunexori into:&lt;/p&gt;

&lt;p&gt;%LOCALAPPDATA%\Programs\Tunexori&lt;/p&gt;

&lt;p&gt;It also creates shortcuts and adds the app to the Windows uninstall list.&lt;/p&gt;

&lt;p&gt;The installer window also shows the app version, so users can see what version they are installing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Requirements&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Tunexori is made for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Windows 10 x64&lt;/li&gt;
&lt;li&gt;Windows 11 x64&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For direct app execution, the app uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;.NET Desktop Runtime 9 x64&lt;/li&gt;
&lt;li&gt;Microsoft Edge WebView2 Runtime x64&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;WebView2 is usually already available on modern Windows 10 and Windows 11 systems. A fallback download link can be&lt;br&gt;
  provided if it is missing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I learned&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This project was a good reminder that small desktop apps still have value.&lt;/p&gt;

&lt;p&gt;A few things that were interesting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WebView2 works well when you want a modern UI inside a native Windows app.&lt;/li&gt;
&lt;li&gt;GitHub Releases can be enough for a simple update system.&lt;/li&gt;
&lt;li&gt;Tray behavior matters a lot for background apps.&lt;/li&gt;
&lt;li&gt;Internet radio streams are inconsistent, so reconnect and status messages are important.&lt;/li&gt;
&lt;li&gt;Simple local JSON settings are enough for this kind of tool.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also learned that small UI details matter. For example, when selecting a station in a long stream list, the list&lt;br&gt;
  should not jump back to the top. That kind of small behavior can make the app feel much better.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Download&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can download Tunexori from GitHub Releases:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/jemontenis/Tunexori/releases" rel="noopener noreferrer"&gt;https://github.com/jemontenis/Tunexori/releases&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you try it and find a problem, you can open an issue here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/jemontenis/Tunexori/issues" rel="noopener noreferrer"&gt;https://github.com/jemontenis/Tunexori/issues&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thanks for reading.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>showdev</category>
      <category>sideprojects</category>
    </item>
  </channel>
</rss>
