Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
97 commits
Select commit Hold shift + click to select a range
6e044e4
docs: improve Android 1.5.0 release notes
Priveetee Aug 22, 2026
969a915
feat: add persistent player control preferences
Priveetee Aug 23, 2026
bacf828
feat: add three-zone player double-tap controls
Priveetee Aug 23, 2026
4a93308
feat: add hardware-aware smart codec fallback
Priveetee Aug 23, 2026
de5b935
feat: apply smart codec selection across playback
Priveetee Aug 23, 2026
8f46388
feat: show the active playback format
Priveetee Aug 23, 2026
3dd621e
fix: create fresh state when switching channels
Priveetee Aug 23, 2026
09a9f41
feat: expand player gesture preferences
Priveetee Aug 23, 2026
445edd7
refactor: prepare player surface for host motion
Priveetee Aug 23, 2026
cba269d
feat: morph player into live mini player
Priveetee Aug 23, 2026
8d2a5ef
test: verify continuous player host motion
Priveetee Aug 23, 2026
d545c4b
fix: preserve player state during autoplay
Priveetee Aug 23, 2026
7dcc745
perf: stabilize player morph rendering
Priveetee Aug 23, 2026
ed230e0
feat: make player controls screen reader accessible
Priveetee Aug 23, 2026
57a1ec3
test: render visible player benchmark frames
Priveetee Aug 23, 2026
7982b9a
feat: continue fullscreen drag into mini player
Priveetee Aug 23, 2026
25ba5d8
fix: restore fullscreen after cancelled drag
Priveetee Aug 23, 2026
c64ad3f
perf: benchmark fullscreen player morph
Priveetee Aug 23, 2026
8595d27
fix: restore portrait after fullscreen gesture
Priveetee Aug 25, 2026
fdfd6fd
fix: restore player host after Shorts
Priveetee Aug 25, 2026
0e99d46
fix: bound playback memory allocation
Priveetee Aug 25, 2026
557e95e
fix: separate fullscreen exit from minimization
Priveetee Aug 25, 2026
01533d4
fix: integrate Shorts subscription action
Priveetee Aug 25, 2026
30c6731
perf: bound decoded image memory
Priveetee Aug 25, 2026
ec463ec
fix: keep video visible in picture in picture
Priveetee Aug 25, 2026
6ba1e92
test: verify portrait fullscreen exit layout
Priveetee Aug 25, 2026
1f24d9e
fix: adapt portrait player controls
Priveetee Aug 25, 2026
0a65b04
feat: expose decoded audio waveform
Priveetee Aug 25, 2026
571c7ff
feat: redesign audio-only playback
Priveetee Aug 25, 2026
e13130f
feat: persist appearance personalities
Priveetee Aug 25, 2026
8968e2e
feat: apply appearance themes globally
Priveetee Aug 25, 2026
11900b9
feat: expose appearance controls
Priveetee Aug 25, 2026
8ce6166
feat: add appearance selection components
Priveetee Aug 25, 2026
8cf8f56
feat: rebuild appearance settings
Priveetee Aug 25, 2026
d7c367c
feat: apply manga details to shared surfaces
Priveetee Aug 25, 2026
472c668
feat: respect appearance motion globally
Priveetee Aug 25, 2026
e5f3f88
refactor: simplify settings surfaces
Priveetee Aug 25, 2026
2320c21
docs: credit Komi Store appearance patterns
Priveetee Aug 25, 2026
92fd88f
refactor: apply appearance shapes across content
Priveetee Aug 25, 2026
44ef1ba
fix: restore playback after leaving Shorts
Priveetee Aug 25, 2026
27dd5c9
fix: restore portrait and fullscreen player gestures
Priveetee Aug 25, 2026
f3363ee
style: refine the Shorts subscription action
Priveetee Aug 25, 2026
942a160
fix: load the actual search history count
Priveetee Aug 25, 2026
75d1d1a
refactor: simplify blocked content rows
Priveetee Aug 25, 2026
02d87f2
refactor: flatten settings detail surfaces
Priveetee Aug 25, 2026
f044bda
refactor: remove YouTube session settings entry
Priveetee Aug 25, 2026
83f3835
fix: bound abandoned SABR playback windows
Priveetee Aug 25, 2026
60166f7
test: stabilize player memory benchmark
Priveetee Aug 25, 2026
848c938
perf: resolve remote media through server
Priveetee Aug 26, 2026
a343f29
feat: derive audio waveform from PCM
Priveetee Aug 26, 2026
86c235e
perf: stabilize player transition rendering
Priveetee Aug 26, 2026
01945b1
feat: add selectable appearance themes
Priveetee Aug 26, 2026
ce7c6df
feat: add Komi-inspired appearance controls
Priveetee Aug 26, 2026
d520d85
refactor: simplify settings surfaces
Priveetee Aug 26, 2026
75d1584
feat: add portability service contract
Priveetee Aug 26, 2026
983e553
test: validate portability mapping
Priveetee Aug 26, 2026
1c5e345
feat: connect portability repository
Priveetee Aug 26, 2026
b49c70c
feat: model portability presentation
Priveetee Aug 26, 2026
2810f12
feat: add portability format selectors
Priveetee Aug 26, 2026
1147a68
feat: add portability import form
Priveetee Aug 26, 2026
2d6d332
feat: show portability job status
Priveetee Aug 26, 2026
d5555ee
feat: coordinate portability jobs
Priveetee Aug 26, 2026
3cc5da0
feat: add portability mode shell
Priveetee Aug 26, 2026
82afa44
feat: add export and import panels
Priveetee Aug 26, 2026
c8638a8
feat: add portability resources and compatibility
Priveetee Aug 26, 2026
44849db
feat: route data portability screen
Priveetee Aug 26, 2026
d8d0396
docs: credit portability format assets
Priveetee Aug 26, 2026
cd0a360
test: cover refreshed player contracts
Priveetee Aug 26, 2026
a3ac092
fix: honor light mode with AMOLED preference
Priveetee Aug 26, 2026
5acbb5b
perf: streamline audio-only playback UI
Priveetee Aug 26, 2026
938f1e9
perf: page blocked items incrementally
Priveetee Aug 26, 2026
23903e5
feat: add export select-all and improve formats
Priveetee Aug 26, 2026
734c51d
fix: normalize playback quality names
Priveetee Aug 26, 2026
0ceb01e
perf: refine portability and audio visuals
Priveetee Aug 26, 2026
a94559e
test: cover portability and quality controls
Priveetee Aug 26, 2026
69f3cb4
fix: handle malformed quality fallback
Priveetee Aug 26, 2026
928979b
chore: bump Android version to 1.6.0
Priveetee Aug 27, 2026
2f18470
perf: lazy-load blocked and related lists
Priveetee Aug 27, 2026
ce8026c
fix: remove duplicate export reset action
Priveetee Aug 27, 2026
e483aa5
docs: add 1.6.0 release notes
Priveetee Aug 27, 2026
b043244
perf: isolate player transition state
Priveetee Aug 27, 2026
de86b6c
perf: bound player comment cache
Priveetee Aug 27, 2026
ccc4a79
perf: defer background preference flows
Priveetee Aug 27, 2026
10641a7
perf: defer token store initialization
Priveetee Aug 27, 2026
75f9a28
perf: show cached startup route immediately
Priveetee Aug 27, 2026
caa8e66
test: measure startup memory usage
Priveetee Aug 27, 2026
f6341e6
feat: add open-source licenses screen
Priveetee Aug 27, 2026
6c69945
test: cover scoped startup landing store
Priveetee Aug 27, 2026
586cb7b
perf: refresh startup baseline profile
Priveetee Aug 27, 2026
01eb37d
docs: explain F-Droid release channels
Priveetee Aug 28, 2026
bf53457
docs: refresh README and onboarding screenshots
Priveetee Aug 28, 2026
86ca85b
docs: refresh all Android screenshots
Priveetee Aug 28, 2026
6d15798
docs: restore Android screenshot set
Priveetee Aug 28, 2026
96adff1
docs: refresh Android screenshots
Priveetee Aug 28, 2026
2a41b8d
docs: update onboarding screenshots
Priveetee Aug 28, 2026
48d6848
fix: keep Manga theme contrast readable
Priveetee Aug 28, 2026
c17fb53
docs: complete Android 1.6.0 release notes
Priveetee Aug 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 41 additions & 69 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,73 +21,62 @@

</div>

TypeType Android is a native client for
TypeType Android is the native client for
[TypeType](https://github.com/TypeType-Video/TypeType), a self-hosted video
platform. It provides browsing, account synchronization, downloads, and native
playback on Android phones and tablets.
platform. The app is in beta, supports Android 6.0 and newer, and does not
require Google Play Services.

The Android app is currently in beta and receives frequent updates.

The app communicates exclusively with the TypeType instance selected during
setup. Extraction, playback sessions, recommendations, synchronization, and
server-side downloads remain server responsibilities.
TypeType Android uses the TypeType Server selected during setup for extraction,
playback sessions, recommendations, synchronization, and downloads.

## Screenshots

| Home and Continue Watching | Search | Subscriptions |
| --- | --- | --- |
| ![TypeType home feed and Continue Watching on Android](assets/screenshots/android-home.png) | ![TypeType video search on Android](assets/screenshots/android-search.png) | ![TypeType subscriptions feed on Android](assets/screenshots/android-subscriptions.png) |

| Library and history | Native player | Comments |
| Welcome | Add an instance | Home |
| --- | --- | --- |
| ![TypeType library and history on Android](assets/screenshots/android-library.png) | ![TypeType native video player on Android](assets/screenshots/android-player.png) | ![TypeType video comments on Android](assets/screenshots/android-comments.png) |
| ![TypeType Android welcome screen](assets/screenshots/android-welcome.png) | ![TypeType Android add instance screen](assets/screenshots/android-add-instance.png) | ![TypeType Android home feed](assets/screenshots/android-home.png) |

| Notifications | Profile | Settings |
| Search | Native player | Settings |
| --- | --- | --- |
| ![TypeType notifications on Android](assets/screenshots/android-notifications.png) | ![TypeType profile and animated avatar support on Android](assets/screenshots/android-profile.png) | ![TypeType settings on Android](assets/screenshots/android-settings.png) |
| ![TypeType Android search screen](assets/screenshots/android-search.png) | ![TypeType Android native video player](assets/screenshots/android-player.png) | ![TypeType Android settings screen](assets/screenshots/android-settings.png) |

## Features

- Resume unfinished videos from Continue Watching.
- Search videos, channels, playlists, music, and other supported content.
- Browse subscriptions with labels for live, premiere, and special videos.
- Access history, favorites, Watch Later, playlists, and notifications.
- Use background audio, Picture in Picture, the mini-player, and audio-only
playback.
- Browse and search videos, channels, playlists, music, and subscriptions.
- Resume videos, manage history, favorites, Watch Later, playlists, and
notifications.
- Play in the background, in Picture in Picture, or with the mini-player.
- Choose quality, codec, audio track, captions, playback speed, and image mode.
- Use chapters, SponsorBlock, the playback queue, comments, related videos, and
the sleep timer.
- Download supported videos and retain cached pages during temporary network
interruptions.
- Import existing data and manage multiple TypeType instances and accounts.

TypeType Android supports Android 6.0 and newer, and does not require Google
Play Services.
- Use chapters, SponsorBlock, comments, related videos, the queue, and the
sleep timer.
- Download supported videos and import existing data.

## Install

### F-Droid

Use the official TypeType F-Droid repository to install the app and receive
stable updates:
TypeType Android is one application with two F-Droid channels:

- **Stable** is the recommended channel for normal use.
- **Beta** is for testing prerelease builds.

Both channels use the same application package and signing certificate, so they
update the same installation. Add only Stable for normal use, or add both
repositories to receive Beta updates. Choose a channel from the
[TypeType F-Droid setup page](https://typetype.video/fdroid/):

1. Open the [TypeType F-Droid setup page](https://typetype.video/fdroid/) on
your Android device, or scan its QR code.
2. Tap **Open in F-Droid**, add the repository, then wait for the catalog to
refresh.
2. Tap **Open in F-Droid**, add the repository, and refresh the catalog.
3. Search for **TypeType** and install it.

### Signed APK

1. Open the
1. Download the signed APK from the
[latest Release](https://github.com/TypeType-Video/TypeType-Android/releases/latest).
2. Download the signed `TypeType-vX.Y.Z.apk`. A matching SHA-256 file is
available if you want to verify the download.
3. Open the APK and allow installation from your browser or file manager when
Android asks.
4. Launch TypeType, enter the address of your TypeType instance, then sign in
with a local account or OIDC. Guest access appears when the instance supports
it.
2. Verify it with the matching SHA-256 file if desired.
3. Open the APK and allow installation when Android asks.
4. Enter your TypeType instance address and sign in, use OIDC, or continue as a
guest when supported.

Installing a newer signed Release over an existing Release keeps the
application data. If Android reports an incompatible signature, remove any
Expand All @@ -99,17 +88,6 @@ Debug build before installing the signed APK.
> access to a compatible TypeType instance. If you want to host one, start with
> the [self-hosting guide](https://typetype-video.github.io/Docs-TypeType/self-hosting/introduction).

## Offline behavior and diagnostics

The app keeps available local cache entries visible while refreshing and
resumes progressive feeds after a temporary interruption. Playback, downloads,
and account operations still depend on the selected instance and its upstream
providers.

When something fails, open **Settings > Diagnostics** to review a redacted,
local request history before sharing it. Diagnostics do not include raw
credentials, tokens, cookies, or private response bodies.

## Help and feedback

- Read the [TypeType user guide](https://typetype-video.github.io/Docs-TypeType/guide/).
Expand All @@ -118,25 +96,17 @@ credentials, tokens, cookies, or private response bodies.
- Report bugs and request features in the
[TypeType Android issue tracker](https://github.com/TypeType-Video/TypeType-Android/issues).

When reporting a playback or network problem, include the Android version, app
version, TypeType instance version, video URL, the action that failed, and the
redacted diagnostics export when available.

## TypeType ecosystem

- [TypeType](https://github.com/TypeType-Video/TypeType), stack installation,
releases, and coordination
- [TypeType-Server](https://github.com/TypeType-Video/TypeType-Server), API,
extraction, playback sessions, and private user data
- [TypeType-Frontend](https://github.com/TypeType-Video/TypeType-Frontend),
browser client
- [Docs-TypeType](https://github.com/TypeType-Video/Docs-TypeType), user and
self-hosting documentation
When reporting a problem, include the Android version, app version, TypeType
Server version, the action that failed, and the redacted diagnostics export
when available.

## Acknowledgements

TypeType Android is an independent client. The following GPL v3 projects have
provided useful technical and product references:
We warmly thank the teams and contributors behind these projects. Their work,
ideas, and hard-earned lessons have been valuable references while building
TypeType Android, especially for Android compatibility, media playback, and
user experience. TypeType Android remains an independent client and is not
affiliated with these projects.

- [PipePipe](https://github.com/InfinityLoop1308/PipePipe) and
[PipePipeClient](https://github.com/InfinityLoop1308/PipePipeClient), for
Expand All @@ -146,6 +116,8 @@ provided useful technical and product references:
UX and Media3 playback lifecycle patterns
- [Findroid](https://github.com/jarnedemeulemeester/findroid), for native
server-first client architecture and multi-server setup patterns
- [Komi Store](https://github.com/kurikomi-labs/komi-store), for appearance
personalities and manga-inspired presentation patterns (Apache-2.0)

## License

Expand Down
19 changes: 19 additions & 0 deletions THIRD_PARTY_ASSETS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Third-party assets

The data portability interface uses the following unmodified project icons for
identification. Each asset remains covered by its source project's license and
trademark rules; this repository's GPL v3 license does not relicense these
files.

| Asset | Source file | Source revision | License |
|---|---|---|---|
| `flow.png` | [Flow icon](https://github.com/A-EDev/Flow/blob/c98a425bd55eca5a8d0edc74981d30c52379fb1b/fastlane/metadata/android/en-US/images/icon.png) | `c98a425bd55eca5a8d0edc74981d30c52379fb1b` | GPL-3.0 |
| `grayjay.png` | [Grayjay launcher icon](https://github.com/futo-org/grayjay-android/blob/3d62258fb2b28f65c3fe7d03357af7c63deb712f/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png) | `3d62258fb2b28f65c3fe7d03357af7c63deb712f` | Source First License 1.1 |
| `materialious.png` | [Materialious launcher icon](https://github.com/Materialious/Materialious/blob/32121b880a7287f70de9ee71634fc11beba52ce4/materialious/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png) | `32121b880a7287f70de9ee71634fc11beba52ce4` | AGPL-3.0 |
| `skytube.png` | [SkyTube launcher icon](https://github.com/SkyTubeTeam/SkyTube/blob/46d9d393f584a5698689dd4e0145058c6ca8801c/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png) | `46d9d393f584a5698689dd4e0145058c6ca8801c` | GPL-3.0 |
| `viewtube.png` | [ViewTube icon](https://github.com/ViewTube/viewtube/blob/2b0183cba46e7a525e363c47bb6df7a62faf5dd1/client/app/assets/icon-192.png) | `2b0183cba46e7a525e363c47bb6df7a62faf5dd1` | AGPL-3.0 |
| `youtube-local.ico` | [youtube-local icon](https://github.com/user234683/youtube-local/blob/1adaf33f4572bbca423ab61d21b5654c8f3b56b9/youtube/static/favicon.ico) | `1adaf33f4572bbca423ab61d21b5654c8f3b56b9` | AGPL-3.0 |

The remaining brand marks come from the `simple-icons` package or from the
TypeType and PipePipe project assets used by TypeType-Frontend at revision
`b5d15841d6544f7e9df343bdbd059caa5258ba3`.
6 changes: 4 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ plugins {
alias(libs.plugins.compose.compiler)
alias(libs.plugins.hilt)
alias(libs.plugins.ksp)
alias(libs.plugins.aboutlibraries)
}

val keystoreProperties = Properties()
Expand All @@ -25,8 +26,8 @@ android {
applicationId = "dev.typetype.android"
minSdk = 23
targetSdk = 37
versionCode = 10500
versionName = "1.5.0"
versionCode = 10600
versionName = "1.6.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
resValue("string", "app_name", "TypeType")
}
Expand Down Expand Up @@ -135,6 +136,7 @@ dependencies {
implementation(libs.androidx.compose.foundation)
implementation(libs.androidx.compose.material.icons.core)
implementation(libs.androidx.compose.material.icons.extended)
implementation(libs.aboutlibraries.compose)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.browser)
implementation(libs.androidx.lifecycle.runtime.compose)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package dev.typetype.android

import androidx.activity.ComponentActivity
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.ui.test.junit4.v2.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.toRoute
import androidx.test.ext.junit.runners.AndroidJUnit4
import dev.typetype.android.core.ui.navigation.ChannelRoute
import dev.typetype.android.core.ui.navigation.HomeRoute
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class ChannelNavigationTest {
@get:Rule
val composeRule = createAndroidComposeRule<ComponentActivity>()

@Test
fun openingAnotherChannelCreatesStateForItsRoute() {
composeRule.setContent {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = HomeRoute) {
composable<HomeRoute> {
Button(onClick = { navController.navigateToChannel(FIRST_CHANNEL) }) {
Text(OPEN_FIRST)
}
}
composable<ChannelRoute> {
val channelViewModel = viewModel<ChannelRouteTestViewModel>()
Column {
Text(channelViewModel.channelUrl)
Button(onClick = { navController.navigateToChannel(SECOND_CHANNEL) }) {
Text(OPEN_SECOND)
}
}
}
}
}

composeRule.onNodeWithText(OPEN_FIRST).performClick()
composeRule.onNodeWithText(FIRST_CHANNEL).assertExists()
composeRule.onNodeWithText(OPEN_SECOND).performClick()
composeRule.onNodeWithText(SECOND_CHANNEL).assertExists()
}

private companion object {
const val OPEN_FIRST = "Open first channel"
const val OPEN_SECOND = "Open second channel"
const val FIRST_CHANNEL = "https://www.youtube.com/@first"
const val SECOND_CHANNEL = "https://www.youtube.com/@second"
}
}

internal class ChannelRouteTestViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {
val channelUrl = savedStateHandle.toRoute<ChannelRoute>().channelUrl
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package dev.typetype.android

import android.app.ActivityManager
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class TypeTypeImageLoaderTest {
@Test
fun imageCacheUsesBoundedApplicationMemory() {
val context = ApplicationProvider.getApplicationContext<Context>()
val memoryClass = context.getSystemService(ActivityManager::class.java).memoryClass
val loader = createTypeTypeImageLoader(context)

try {
assertEquals(
(memoryClass * 1024L * 1024L * EXPECTED_CACHE_PERCENT).toLong(),
loader.memoryCache?.maxSize,
)
} finally {
loader.shutdown()
}
}
}

private const val EXPECTED_CACHE_PERCENT = 0.10
Loading