Skip to content

Commit f00ead3

Browse files
committed
feat: enhance browser functionality and stability
- Enable hardware acceleration for performance - Implement YouTube-specific ad blocking - Add notification permission handling for media - Improve WebView background playback visibility - Add tab preference persistence test coverage
1 parent e6a37d2 commit f00ead3

15 files changed

Lines changed: 857 additions & 154 deletions

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [1.0.9] - 2026-09-03
9+
10+
### Added & Enhanced
11+
- **🎵 YouTube & Web Background Playback**:
12+
- Uninterrupted audio and media playback when switching tabs, navigating to the home screen, or turning the device screen off.
13+
- Seamless multi-tab persistent WebViews ensuring background media does not restart or pause during tab switches.
14+
- **📱 System Media Player & Lock Screen Controls**:
15+
- Full Android `MediaSession` integration with rich notification bar and lock screen player.
16+
- Controls for Play, Pause, Next Track, Previous Track, and Close.
17+
- Dynamic extraction and display of video title, artist/channel name, and high-resolution thumbnail artwork (`hqdefault.jpg`).
18+
- Android 13+ dynamic `POST_NOTIFICATIONS` runtime permission handling.
19+
- **🛡️ Enhanced YouTube Ad Blocker**:
20+
- Network-level interception for YouTube video ad streams, tracking calls, and DoubleClick endpoints.
21+
- Client-side DOM filtering suppressing promoted cards, banner overlays, and companion ads.
22+
- Automatic skip detection and fast-forwarding through unskippable video ad segments.
23+
824
## [1.0.8] - 2026-09-01
925

1026
### Added & Enhanced

README.md

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,25 @@
66
<strong>An ultra-lightweight (~1.3 MB), high-performance, privacy-first Android browser featuring true isolated multi-profiles with custom device fingerprint spoofing.</strong>
77
</p>
88

9-
[![GitHub Release](https://img.shields.io/github/v/release/aanneion/lightweight_browser?style=for-the-badge&color=2563EB&logo=github)](https://github.com/aanneion/lightweight_browser/releases/latest)
10-
[![CI Status](https://img.shields.io/github/actions/workflow/status/aanneion/lightweight_browser/ci.yml?branch=main&style=for-the-badge&logo=githubactions&logoColor=white)](https://github.com/aanneion/lightweight_browser/actions)
11-
[![APK Size](https://img.shields.io/badge/APK_Size-~1.3_MB-10B981?style=for-the-badge&logo=android&logoColor=white)](https://github.com/aanneion/lightweight_browser/releases/latest)
9+
[![Releases](https://img.shields.io/badge/Releases-Latest%20APKs-2563EB?style=for-the-badge&logo=github)](https://github.com/aanneion/lightweight_browser/releases)
10+
[![Build Status](https://img.shields.io/github/actions/workflow/status/aanneion/lightweight_browser/release.yml?branch=main&style=for-the-badge&logo=githubactions&logoColor=white)](https://github.com/aanneion/lightweight_browser/actions)
11+
[![APK Size](https://img.shields.io/badge/APK_Size-~1.3_MB-10B981?style=for-the-badge&logo=android&logoColor=white)](https://github.com/aanneion/lightweight_browser/releases)
1212
[![Platform](https://img.shields.io/badge/Platform-Android_8.0+_(API_24+)-3DDC84?style=for-the-badge&logo=android&logoColor=white)](https://www.android.com/)
1313
[![Kotlin](https://img.shields.io/badge/Kotlin-2.2-7F52FF?style=for-the-badge&logo=kotlin&logoColor=white)](https://kotlinlang.org/)
1414
[![Jetpack Compose](https://img.shields.io/badge/UI-Jetpack_Compose_M3-4285F4?style=for-the-badge&logo=jetpackcompose&logoColor=white)](https://developer.android.com/jetpack/compose)
1515
[![License](https://img.shields.io/badge/License-MIT-F59E0B?style=for-the-badge)](LICENSE)
1616

1717
<br />
1818

19-
[📥 Download Latest APK (v1.0.8)](https://github.com/aanneion/lightweight_browser/releases/latest)[🌟 Core Differentiators](#-core-differentiators)[🛡️ Multi-Profile Isolation](#-isolated-multi-profiles--fingerprint-spoofing)[✨ Feature Highlights](#-feature-highlights)[🛠️ Architecture](#-architecture--tech-stack)[🚀 Building from Source](#-building-from-source)
19+
[🌟 Core Differentiators](#-core-differentiators)[🛡️ Multi-Profile Isolation](#-isolated-multi-profiles--fingerprint-spoofing)[✨ Feature Highlights](#-feature-highlights)[🛠️ Architecture](#-architecture--tech-stack)[🚀 Building from Source](#-building-from-source)
2020

2121
---
2222

2323
</div>
2424

2525
## 📖 Overview
2626

27-
**Feather Browser** is an open-source, minimalist web browser engineered from the ground up for speed, low resource consumption, and uncompromised privacy. Unlike mainstream mobile browsers that consume hundreds of megabytes of storage and track user activity, Feather Browser compiles into a lean **~1.3 MB APK** while offering desktop-grade features like **Isolated Multi-Profiles**, **Device Fingerprint Spoofing**, and a **Built-in Ad & Tracker Blocker**.
27+
**Feather Browser** is an open-source, minimalist web browser engineered from the ground up for speed, low resource consumption, and uncompromised privacy. Unlike mainstream mobile browsers that consume hundreds of megabytes of storage and track user activity, Feather Browser compiles into a lean **~1.3 MB APK** while offering desktop-grade features like **Isolated Multi-Profiles**, **Device Fingerprint Spoofing**, a **Built-in Ad & Tracker Blocker**, and **Background YouTube & Media Playback** with lock screen controls.
2828

2929
Built purely in **Kotlin** and **Jetpack Compose (Material Design 3)**, Feather delivers dynamic Material You theming, instant cold starts, and buttery-smooth 120Hz scrolling.
3030

@@ -37,7 +37,8 @@ Built purely in **Kotlin** and **Jetpack Compose (Material Design 3)**, Feather
3737
| 🪶 **Ultra-Lightweight (~1.3 MB)** | 90%+ smaller than standard browsers (Chrome ~150MB, Firefox ~90MB). Stripped of unnecessary bloat, third-party analytics SDKs, and background daemon services. |
3838
| 👥 **True Multi-Profile Isolation** | Separate browser personas within a single app. Each profile maintains its own dedicated sandbox: independent cookies, localStorage, indexedDB, web cache, tabs, bookmarks, and history. |
3939
| 🎭 **Hardware & Fingerprint Spoofing** | Each profile can emulate distinct hardware/platform signatures (Windows, macOS Safari, iOS, Linux Firefox, Stealth) to defeat browser fingerprinting and cross-site tracking. |
40-
| 🛡️ **Zero-Overhead Content Blocker** | Inbuilt network-level request interceptor that blocks ads, tracking pixels, and telemetry beacons before network requests are dispatched. |
40+
| 🎵 **YouTube & Background Media Play** | Seamless background audio playback when screen is locked or browsing other tabs. Full Android `MediaSession` lock screen and notification bar controls. |
41+
| 🛡️ **Enhanced Ad & YouTube Blocker** | Inbuilt network-level request interceptor and DOM cleaner that removes web trackers, banner ads, and blocks YouTube video prerolls, midrolls, and promoted videos with auto-skipping. |
4142
|**Instant Cold Boot & Low RAM Usage** | Near-zero startup latency with optimized R8 tree-shaking and memory-efficient Compose layout trees. |
4243

4344
---
@@ -78,8 +79,11 @@ Most mobile browsers share a single global cookie jar and device identity across
7879

7980
## ✨ Feature Highlights
8081

81-
- 🛡️ **Native Ad & Tracker Blocker**: Blocks intrusive pop-ups, banner networks, and telemetry scripts without requiring heavy extensions.
82+
- 🎵 **YouTube & Background Media Playback**: Continue playing audio from YouTube and web media seamlessly in the background when switching tabs, minimizing the browser, or locking the device.
83+
- 📱 **Lock Screen & Notification Player Controls**: Full Android `MediaSession` integration providing lock screen playback controls, dynamic video title and artist display, high-resolution artwork thumbnails, and responsive play/pause/skip actions.
84+
- 🛡️ **Enhanced YouTube & Web Ad Blocker**: Dual-layer blocking engine combining fast network URL interception with client-side DOM cleansing to block YouTube video ads, banner promos, pop-ups, and trackers without battery overhead.
8285
- 🎨 **Material You Dynamic Theming**: Adapts seamlessly to your device's Android 12+ wallpaper colors, with Light, Dark, and true Pitch-Black (`#000000`) AMOLED modes.
86+
- 👥 **True Isolated Multi-Profiles**: Independent browser personas with partitioned cookies, cache, local storage, history, bookmarks, and per-profile hardware fingerprint presets.
8387
- 🕵️ **Ephemeral Private Browsing**: One-tap incognito tabs that wipe cookies, session tokens, and cache immediately upon closing.
8488
- 🔍 **Multi-Engine Search Selector**: Switch instantly between Google, DuckDuckGo, Brave Search, and Bing directly from search settings.
8589
- 📑 **Visual Tabs Manager**: Intuitive tab switcher with real-time website favicons, tab counts, and swift swipe-to-dismiss gestures.
@@ -144,6 +148,9 @@ Feather Browser only requests permissions strictly required for browsing:
144148
| :--- | :--- |
145149
| `android.permission.INTERNET` | Required to fetch web pages requested by the user. |
146150
| `android.permission.ACCESS_NETWORK_STATE` | Used to detect offline state and trigger network reconnects. |
151+
| `android.permission.FOREGROUND_SERVICE` | Required for persistent background audio playback when the app is minimized or screen is locked. |
152+
| `android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK` | Android 14+ specific foreground service type for media and audio playback. |
153+
| `android.permission.POST_NOTIFICATIONS` | Displays media playback notification player with controls on Android 13+. |
147154

148155
> **Privacy Guarantee:** Feather Browser collects **0% telemetry**. No URLs, search queries, IP addresses, or device analytics are ever logged, sent to external servers, or sold. Everything stays strictly on your physical device.
149156

app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
<application
1212
android:allowBackup="true"
13+
android:hardwareAccelerated="true"
1314
android:dataExtractionRules="@xml/data_extraction_rules"
1415
android:fullBackupContent="@xml/backup_rules"
1516
android:icon="@mipmap/ic_launcher"

app/src/main/java/com/example/MainActivity.kt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,43 @@
11
package com.example
22

3+
import android.Manifest
34
import android.content.Intent
5+
import android.content.pm.PackageManager
6+
import android.os.Build
47
import android.os.Bundle
58
import androidx.activity.ComponentActivity
69
import androidx.activity.compose.setContent
710
import androidx.activity.enableEdgeToEdge
11+
import androidx.activity.result.contract.ActivityResultContracts
812
import androidx.activity.viewModels
913
import androidx.compose.foundation.layout.fillMaxSize
1014
import androidx.compose.runtime.LaunchedEffect
1115
import androidx.compose.runtime.getValue
1216
import androidx.compose.ui.Modifier
17+
import androidx.core.content.ContextCompat
1318
import androidx.lifecycle.compose.collectAsStateWithLifecycle
1419
import com.example.browser.BrowserViewModel
20+
import com.example.media.MediaSessionManager
1521
import com.example.ui.BrowserScreen
1622
import com.example.ui.theme.MyApplicationTheme
1723

1824
class MainActivity : ComponentActivity() {
1925

2026
private val viewModel: BrowserViewModel by viewModels()
2127

28+
private val notificationPermissionLauncher = registerForActivityResult(
29+
ActivityResultContracts.RequestPermission()
30+
) { isGranted ->
31+
if (isGranted) {
32+
MediaSessionManager.refreshNotification(this)
33+
}
34+
}
35+
2236
override fun onCreate(savedInstanceState: Bundle?) {
2337
super.onCreate(savedInstanceState)
2438
enableEdgeToEdge()
2539

40+
requestNotificationPermissionIfNeeded()
2641
handleIntent(intent)
2742

2843
setContent {
@@ -43,6 +58,14 @@ class MainActivity : ComponentActivity() {
4358
}
4459
}
4560

61+
private fun requestNotificationPermissionIfNeeded() {
62+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
63+
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
64+
notificationPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
65+
}
66+
}
67+
}
68+
4669
override fun onNewIntent(intent: Intent) {
4770
super.onNewIntent(intent)
4871
setIntent(intent)

app/src/main/java/com/example/browser/BrowserViewModel.kt

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import android.widget.Toast
1212
import androidx.lifecycle.AndroidViewModel
1313
import androidx.lifecycle.viewModelScope
1414
import com.example.data.AppDatabase
15+
import com.example.data.BrowserPreferences
1516
import com.example.data.BrowserRepository
1617
import com.example.data.model.*
1718
import com.example.privacy.ContentBlocker
@@ -85,36 +86,69 @@ class BrowserViewModel(application: Application) : AndroidViewModel(application)
8586
private val _findQuery = MutableStateFlow("")
8687
val findQuery: StateFlow<String> = _findQuery.asStateFlow()
8788

89+
// Settings Preferences
90+
val preferences = BrowserPreferences(application)
91+
8892
// Settings State
89-
val searchEngine = MutableStateFlow(SearchEngine.GOOGLE)
90-
val themeMode = MutableStateFlow(AppThemeMode.SYSTEM)
91-
val useMaterialYou = MutableStateFlow(true)
92-
val newTabStyle = MutableStateFlow(NewTabStyle.PRODUCTIVITY)
93-
val isAdBlockEnabled = MutableStateFlow(true)
94-
val blockThirdPartyCookies = MutableStateFlow(true)
95-
val httpsMode = MutableStateFlow(HttpsMode.PREFER_HTTPS)
96-
val enableWebDarkMode = MutableStateFlow(false)
97-
val enableBackgroundPlay = MutableStateFlow(true)
98-
val downloadProvider = MutableStateFlow(DownloadProvider.BUILT_IN)
93+
val searchEngine = MutableStateFlow(preferences.getSearchEngine())
94+
val themeMode = MutableStateFlow(preferences.getThemeMode())
95+
val useMaterialYou = MutableStateFlow(preferences.getUseMaterialYou())
96+
val newTabStyle = MutableStateFlow(preferences.getNewTabStyle())
97+
val isAdBlockEnabled = MutableStateFlow(preferences.isAdBlockEnabled())
98+
val blockThirdPartyCookies = MutableStateFlow(preferences.isBlockThirdPartyCookies())
99+
val httpsMode = MutableStateFlow(preferences.getHttpsMode())
100+
val enableWebDarkMode = MutableStateFlow(preferences.isEnableWebDarkMode())
101+
val enableBackgroundPlay = MutableStateFlow(preferences.isEnableBackgroundPlay())
102+
val downloadProvider = MutableStateFlow(preferences.getDownloadProvider())
103+
104+
fun setSearchEngine(engine: SearchEngine) {
105+
searchEngine.value = engine
106+
preferences.setSearchEngine(engine)
107+
}
99108

100109
fun setThemeMode(mode: AppThemeMode) {
101110
themeMode.value = mode
111+
preferences.setThemeMode(mode)
102112
}
103113

104114
fun setUseMaterialYou(enabled: Boolean) {
105115
useMaterialYou.value = enabled
116+
preferences.setUseMaterialYou(enabled)
106117
}
107118

108119
fun setNewTabStyle(style: NewTabStyle) {
109120
newTabStyle.value = style
121+
preferences.setNewTabStyle(style)
122+
}
123+
124+
fun setAdBlockEnabled(enabled: Boolean) {
125+
isAdBlockEnabled.value = enabled
126+
preferences.setAdBlockEnabled(enabled)
127+
}
128+
129+
fun setBlockThirdPartyCookies(enabled: Boolean) {
130+
blockThirdPartyCookies.value = enabled
131+
preferences.setBlockThirdPartyCookies(enabled)
132+
}
133+
134+
fun setHttpsMode(mode: HttpsMode) {
135+
httpsMode.value = mode
136+
preferences.setHttpsMode(mode)
137+
}
138+
139+
fun setEnableWebDarkMode(enabled: Boolean) {
140+
enableWebDarkMode.value = enabled
141+
preferences.setEnableWebDarkMode(enabled)
110142
}
111143

112144
fun setBackgroundPlay(enabled: Boolean) {
113145
enableBackgroundPlay.value = enabled
146+
preferences.setEnableBackgroundPlay(enabled)
114147
}
115148

116149
fun setDownloadProvider(provider: DownloadProvider) {
117150
downloadProvider.value = provider
151+
preferences.setDownloadProvider(provider)
118152
}
119153

120154
// Whitelisted domains flow
@@ -389,6 +423,11 @@ class BrowserViewModel(application: Application) : AndroidViewModel(application)
389423
viewModelScope.launch {
390424
repository.deleteTab(tabId)
391425
ContentBlocker.resetTabBlockCount(tabId)
426+
try {
427+
if (com.example.media.MediaSessionManager.activeMediaTabId.value == tabId) {
428+
com.example.media.MediaSessionManager.onMediaEnded(context, tabId)
429+
}
430+
} catch (e: Throwable) { }
392431
val remaining = currentTabs.value.filter { it.id != tabId }
393432
if (remaining.isNotEmpty()) {
394433
if (_activeTabId.value == tabId) {
@@ -402,6 +441,9 @@ class BrowserViewModel(application: Application) : AndroidViewModel(application)
402441

403442
fun closeAllTabs() {
404443
viewModelScope.launch {
444+
try {
445+
com.example.media.MediaSessionManager.stopPlayback(context)
446+
} catch (e: Throwable) { }
405447
if (_isPrivateMode.value) {
406448
repository.clearPrivateTabs()
407449
privacyManager.cleanPrivateSessionData()

0 commit comments

Comments
 (0)