Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
8b899a1
fix: share SABR initialization with Android playback
Priveetee Jul 26, 2026
8585c2d
refactor: remove dedicated Android playback API
Priveetee Jul 26, 2026
d813f59
refactor: remove Android VR extraction client
Priveetee Jul 26, 2026
14ff7fc
fix: maintain live SABR read ahead
Priveetee Jul 27, 2026
99e98b5
fix: align live SABR playback windows
Priveetee Jul 27, 2026
55ba957
test: cover continuous live SABR pumping
Priveetee Jul 27, 2026
dd3f90a
test: cover live SABR warmup continuity
Priveetee Jul 27, 2026
de149ed
test: cover live SABR playback windows
Priveetee Jul 27, 2026
4ab7348
test: add live SABR continuity probe
Priveetee Jul 27, 2026
4b26668
chore: prepare server 1.2.2 version
Priveetee Jul 27, 2026
624f90d
fix: propagate playback rate through SABR
Priveetee Jul 27, 2026
b8239a7
fix: preserve selected SABR video format
Priveetee Jul 27, 2026
7dcfa89
docs: document granular SABR playback state
Priveetee Jul 27, 2026
d39ce2c
chore: prepare server 1.2.3 version
Priveetee Jul 27, 2026
9b832db
chore: update PipePipeExtractor
Priveetee Jul 28, 2026
b771a18
fix: bound SABR playback preparation
Priveetee Jul 28, 2026
649391b
test: cover stalled SABR initialization
Priveetee Jul 28, 2026
07bd1cf
fix: classify historical livestream videos as vod
Priveetee Jul 28, 2026
4bf9360
fix: preserve vod mode across format changes
Priveetee Jul 28, 2026
4fba3e0
refactor: remove classic YouTube playback routes
Priveetee Jul 28, 2026
cec79ef
fix: preserve live feed chronology
Priveetee Jul 28, 2026
8db661a
fix: preserve Takeout favorite chronology
Priveetee Jul 28, 2026
b6e82b2
fix: isolate SABR playback source identities
Priveetee Jul 29, 2026
22bcc8c
fix: recover SABR sessions after upstream authorization
Priveetee Jul 29, 2026
6706b51
feat: add framed SABR download protocol
Priveetee Jul 29, 2026
a6f0c90
feat: stream bounded SABR download parts
Priveetee Jul 29, 2026
ae4cf1b
feat: expose native SABR download streams
Priveetee Jul 29, 2026
9cb0170
test: cover streamed SABR download sessions
Priveetee Jul 29, 2026
3555594
perf: remove unused SABR audio materializer
Priveetee Jul 29, 2026
3631998
chore: refresh server build dependencies
Priveetee Jul 29, 2026
51374a5
fix: publish partial subscription feed refreshes
Priveetee Jul 29, 2026
23e236e
feat: persist default playback speed
Priveetee Jul 29, 2026
da72c16
feat: block recommendation title keywords
Priveetee Jul 29, 2026
3797a6b
feat: define native TypeType backups
Priveetee Jul 29, 2026
9ebc0c7
feat: restore TypeType library data
Priveetee Jul 29, 2026
928d363
feat: restore TypeType content filters
Priveetee Jul 29, 2026
3978971
feat: expose native TypeType backups
Priveetee Jul 29, 2026
0090b5e
docs: document user data controls
Priveetee Jul 29, 2026
1be4c59
test: cover native TypeType backups
Priveetee Jul 29, 2026
e2be7c0
fix: preserve imported favorite dates
Priveetee Jul 29, 2026
7af1af0
test: preserve user data when clearing history
Priveetee Jul 29, 2026
a5f802a
fix: export large watch histories
Priveetee Jul 29, 2026
100b430
fix: bound stalled SABR download pumps
Priveetee Jul 29, 2026
7a8611c
Merge remote-tracking branch 'origin/main' into release/1.3.0
Priveetee Jul 30, 2026
a1a17d6
chore: prepare server 1.3.0
Priveetee Jul 30, 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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
build:
runs-on: ${{ github.event_name == 'pull_request' && 'ubuntu-24.04' || fromJSON('["self-hosted","Linux","X64","arko"]') }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7

- name: Isolate Gradle user home
run: echo "GRADLE_USER_HOME=${RUNNER_TEMP}/gradle-user-home" >> "$GITHUB_ENV"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
coverage:
runs-on: ${{ github.event_name == 'pull_request' && 'ubuntu-24.04' || fromJSON('["self-hosted","Linux","X64","arko"]') }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7

- name: Isolate Gradle user home
run: echo "GRADLE_USER_HOME=${RUNNER_TEMP}/gradle-user-home" >> "$GITHUB_ENV"
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:

steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7

- name: Resolve build metadata
id: build-info
Expand Down Expand Up @@ -92,7 +92,7 @@ jobs:

steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7

- name: Isolate Docker credentials
run: echo "DOCKER_CONFIG=$RUNNER_TEMP/docker-config" >> "$GITHUB_ENV"
Expand All @@ -106,7 +106,7 @@ jobs:

- name: Set up QEMU
if: matrix.arch == 'arm64'
uses: docker/setup-qemu-action@v4.1.0
uses: docker/setup-qemu-action@v4.2.0
with:
platforms: arm64
cache-image: false
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/openapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
validate:
runs-on: ${{ github.event_name == 'pull_request' && 'ubuntu-24.04' || fromJSON('["self-hosted","Linux","X64","arko"]') }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Isolate Gradle user home
run: echo "GRADLE_USER_HOME=${RUNNER_TEMP}/gradle-user-home" >> "$GITHUB_ENV"
- uses: actions/setup-java@v5
Expand Down
4 changes: 3 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ COPY src/ ./src/
RUN ./gradlew shadowJar --no-daemon -q -PappVersion="$APP_VERSION"

FROM eclipse-temurin:25-jre-alpine AS runner
RUN addgroup -S typetype && adduser -S typetype -G typetype
RUN apk upgrade --no-cache \
&& addgroup -S typetype \
&& adduser -S typetype -G typetype
WORKDIR /app
COPY --from=builder /app/build/libs/typetype-server-all.jar app.jar
USER typetype
Expand Down
18 changes: 10 additions & 8 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import java.time.Instant

plugins {
kotlin("jvm") version "2.4.0"
kotlin("plugin.serialization") version "2.4.0"
kotlin("jvm") version "2.4.10"
kotlin("plugin.serialization") version "2.4.10"
id("io.ktor.plugin") version "3.5.1"
id("jacoco")
}
Expand All @@ -25,6 +25,8 @@ repositories {
}

dependencies {
implementation(platform("com.fasterxml.jackson:jackson-bom:2.22.1"))
implementation(platform("io.netty:netty-bom:4.2.16.Final"))
implementation("io.ktor:ktor-server-core-jvm")
implementation("io.ktor:ktor-server-netty-jvm")
implementation("io.ktor:ktor-server-content-negotiation-jvm")
Expand All @@ -35,20 +37,20 @@ dependencies {
implementation("io.ktor:ktor-server-status-pages-jvm")
implementation("io.ktor:ktor-server-call-logging-jvm")
implementation("io.ktor:ktor-server-rate-limit-jvm")
implementation("ch.qos.logback:logback-classic:1.5.38")
implementation("ch.qos.logback:logback-classic:1.6.1")
implementation("com.github.Priveetee.PipePipeExtractor:extractor:21ab9b6ac415d23ef88df19c5ad126c7952bb543")
compileOnly("com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751")
implementation("org.json:json:20260522")
implementation("org.json:json:20260719")
implementation("com.squareup.okhttp3:okhttp:5.4.0")
implementation("io.lettuce:lettuce-core:7.6.0.RELEASE")
implementation("org.jetbrains.exposed:exposed-core:1.3.1")
implementation("org.jetbrains.exposed:exposed-jdbc:1.3.1")
implementation("com.zaxxer:HikariCP:7.1.0")
implementation("org.postgresql:postgresql:42.7.13")
implementation("org.xerial:sqlite-jdbc:3.53.2.0")
implementation("org.xerial:sqlite-jdbc:3.53.2.1")
implementation("com.password4j:password4j:1.8.4")
implementation("com.auth0:java-jwt:4.5.2")
testImplementation("org.junit.jupiter:junit-jupiter:6.1.1")
implementation("com.auth0:java-jwt:4.6.0")
testImplementation("org.junit.jupiter:junit-jupiter:6.1.2")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
testImplementation("io.mockk:mockk:1.14.11")
testImplementation("io.ktor:ktor-server-test-host-jvm")
Expand Down Expand Up @@ -112,7 +114,7 @@ tasks.test {
}

jacoco {
toolVersion = "0.8.14"
toolVersion = "0.8.15"
}

tasks.jacocoTestReport {
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.arm64.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
services:
dragonfly:
image: docker.dragonflydb.io/dragonflydb/dragonfly:latest
image: docker.dragonflydb.io/dragonflydb/dragonfly:v1.39.0
platform: linux/arm64
2 changes: 1 addition & 1 deletion docker-compose.dev-mirror.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ services:
fi
restart: "no"
dragonfly:
image: docker.dragonflydb.io/dragonflydb/dragonfly:latest
image: docker.dragonflydb.io/dragonflydb/dragonfly:v1.39.0
ulimits: { memlock: -1 }
restart: unless-stopped
garage:
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
services:
dragonfly:
image: docker.dragonflydb.io/dragonflydb/dragonfly:latest
image: docker.dragonflydb.io/dragonflydb/dragonfly:v1.39.0
ports:
- "6379:6379"
ulimits:
Expand Down
43 changes: 0 additions & 43 deletions docs/android-playback.md

This file was deleted.

2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
org.gradle.jvmargs=-Xmx2g -XX:+UseG1GC
kotlin.code.style=official
appVersion=1.2.4
appVersion=1.3.0
systemProp.sun.net.client.defaultReadTimeout=180000
systemProp.sun.net.client.defaultConnectTimeout=60000
24 changes: 8 additions & 16 deletions openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,17 @@ tags:
- name: playback
- name: downloader
- name: youtube-session
- name: android-playback
- name: user-data
paths:
/health: { $ref: ./openapi/paths/health.yaml#/Health }
/instance: { $ref: ./openapi/paths/metadata.yaml#/Instance }
/streams: { $ref: ./openapi/paths/streams.yaml#/Streams }
/streams/legacy: { $ref: ./openapi/paths/streams.yaml#/LegacyStreams }
/streams/youtube/sabr: { $ref: ./openapi/paths/streams.yaml#/YoutubeSabrStreams }
/streams/youtube/sabr/bootstrap: { $ref: ./openapi/paths/streams.yaml#/YoutubeSabrBootstrap }
/streams/youtube/legacy: { $ref: ./openapi/paths/streams.yaml#/YoutubeLegacyStreams }
/streams/niconico: { $ref: ./openapi/paths/streams.yaml#/NicoNicoStreams }
/streams/bilibili: { $ref: ./openapi/paths/streams.yaml#/BiliBiliStreams }
/streams/audio-only: { $ref: ./openapi/paths/streams.yaml#/AudioOnly }
/streams/audio-only/source: { $ref: ./openapi/paths/streams.yaml#/AudioOnlySource }
/android/youtube/playback/{videoId}: { $ref: ./openapi/paths/android-playback.yaml#/Create }
/android/youtube/playback/{sessionId}/seek: { $ref: ./openapi/paths/android-playback.yaml#/Seek }
/android/youtube/playback/{sessionId}/manifest.mpd: { $ref: ./openapi/paths/android-playback.yaml#/Manifest }
/android/youtube/playback/{sessionId}/subtitles/{trackId}.vtt: { $ref: ./openapi/paths/android-playback.yaml#/Subtitle }
/android/youtube/playback/{sessionId}/{itag}/init: { $ref: ./openapi/paths/android-playback.yaml#/Initialization }
/android/youtube/playback/{sessionId}/{itag}/segment/{sequence}: { $ref: ./openapi/paths/android-playback.yaml#/Segment }
/sabr/download/{videoId}: { $ref: ./openapi/paths/sabr-download.yaml#/SabrDownload }
/sabr/playback/{sessionId}/position: { $ref: ./openapi/paths/sabr-playback.yaml#/SabrPlaybackPosition }
/sabr/playback/{sessionId}/window: { $ref: ./openapi/paths/sabr-playback.yaml#/SabrPlaybackWindow }
/sabr/playback/{sessionId}/prefetch: { $ref: ./openapi/paths/sabr-playback.yaml#/SabrPlaybackPrefetch }
Expand All @@ -51,6 +42,10 @@ paths:
/saved-playlists/{id}: { $ref: ./openapi/paths/saved-playlists.yaml#/SavedPlaylist }
/subscriptions/feed: { $ref: ./openapi/paths/subscriptions.yaml#/SubscriptionFeed }
/settings: { $ref: ./openapi/paths/access-control.yaml#/Settings }
/backup/typetype: { $ref: ./openapi/paths/user-backup.yaml#/TypeTypeBackup }
/restore/typetype: { $ref: ./openapi/paths/user-backup.yaml#/TypeTypeRestore }
/blocked/keywords: { $ref: ./openapi/paths/user-backup.yaml#/BlockedKeywords }
/blocked/keywords/{keyword}: { $ref: ./openapi/paths/user-backup.yaml#/BlockedKeyword }
/allowed/channels: { $ref: ./openapi/paths/access-control.yaml#/AllowedChannels }
/allowed/channels/{channelUrl}: { $ref: ./openapi/paths/access-control.yaml#/AllowedChannel }
/admin/settings: { $ref: ./openapi/paths/access-control.yaml#/AdminSettings }
Expand Down Expand Up @@ -101,12 +96,6 @@ components:
$ref: ./openapi/components/instance.yaml#/InstanceMinClientVersion
InstanceResponse:
$ref: ./openapi/components/instance.yaml#/InstanceResponse
AndroidPlaybackCreateRequest:
$ref: ./openapi/components/android-playback.yaml#/AndroidPlaybackCreateRequest
AndroidPlaybackSeekRequest:
$ref: ./openapi/components/android-playback.yaml#/AndroidPlaybackSeekRequest
AndroidPlaybackResponse:
$ref: ./openapi/components/android-playback.yaml#/AndroidPlaybackResponse
StreamResponse:
$ref: ./openapi/components/streams.yaml#/StreamResponse
AudioOnlyStreamResponse:
Expand Down Expand Up @@ -144,6 +133,9 @@ components:
SubscriptionFeedResponse: { $ref: ./openapi/components/subscriptions.yaml#/SubscriptionFeedResponse }
SubscriptionFeedPreparingResponse: { $ref: ./openapi/components/subscriptions.yaml#/SubscriptionFeedPreparingResponse }
SettingsItem: { $ref: ./openapi/components/access-control.yaml#/SettingsItem }
TypeTypeBackupItem: { $ref: ./openapi/components/user-backup.yaml#/TypeTypeBackupItem }
TypeTypeRestoreSummary: { $ref: ./openapi/components/user-backup.yaml#/TypeTypeRestoreSummary }
BlockedKeywordItem: { $ref: ./openapi/components/user-backup.yaml#/BlockedKeywordItem }
AdminSettingsItem: { $ref: ./openapi/components/access-control.yaml#/AdminSettingsItem }
AllowedChannelItem: { $ref: ./openapi/components/access-control.yaml#/AllowedChannelItem }
CommentsPageResponse: { $ref: ./openapi/components/media.yaml#/CommentsPageResponse }
Expand Down
6 changes: 6 additions & 0 deletions openapi/components/access-control.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ SettingsItem:
properties:
defaultService: { type: integer, default: 0 }
defaultQuality: { type: string, default: 1080p }
defaultPlaybackSpeed:
type: number
format: double
minimum: 0.25
maximum: 4
default: 1
defaultLandingPage: { type: string, default: home }
autoplay: { type: boolean, default: true }
volume: { type: number, format: double, default: 1.0 }
Expand Down
56 changes: 0 additions & 56 deletions openapi/components/android-playback.yaml

This file was deleted.

26 changes: 0 additions & 26 deletions openapi/components/instance.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ InstanceResponse:
- oidcAutoRedirect
- youtubeRemoteLoginEnabled
- youtubeRemoteLoginReady
- androidPlayback
properties:
name: { type: string, example: TypeType }
tagline: { type: string, nullable: true }
Expand Down Expand Up @@ -50,28 +49,3 @@ InstanceResponse:
type: string
nullable: true
enum: [disabled, not_configured, token_unreachable]
androidPlayback:
$ref: '#/AndroidPlaybackCapability'
AndroidPlaybackCapability:
type: object
required: [supported, contractVersion, youtube]
properties:
supported: { type: boolean, example: true }
contractVersion: { type: integer, enum: [4] }
youtube:
$ref: '#/AndroidYoutubePlaybackCapability'
AndroidYoutubePlaybackCapability:
type: object
required: [vod, live, subtitles, deferredSubtitleContent, bootstrapSubtitleDescriptors]
properties:
vod: { type: boolean, example: true }
live: { type: boolean, example: false }
subtitles: { type: boolean, example: true }
deferredSubtitleContent:
type: boolean
example: true
description: Subtitle documents are fetched only when a session-scoped WebVTT resource is requested.
bootstrapSubtitleDescriptors:
type: boolean
example: true
description: Every successful playback creation response contains the complete subtitle descriptor catalog.
Loading