personal web client for Bluesky
typescript solidjs bluesky atcute

Compare changes

Choose any two refs to compare.

Changed files
+86 -7
src
components
lib
utils
+12 -6
src/components/embeds/players/video-player.tsx
··· 8 9 import { replaceVideoCdnUrl } from '~/lib/bsky/video'; 10 import { useSession } from '~/lib/states/session'; 11 12 const isMobile = /Android|iPhone|iPad|iPod/.test(navigator.userAgent); 13 ··· 21 22 const [playing, setPlaying] = createSignal(false); 23 24 - const bwEstimate = currentAccount?.preferences.ui.videoBwEstimate; 25 const hls = new Hls({ 26 capLevelToPlayerSize: true, 27 ··· 70 node.volume = currentAccount.preferences.ui.mediaVolume; 71 } 72 73 - hls.on(Hls.Events.FRAG_LOADED, () => { 74 - if (currentAccount) { 75 - currentAccount.preferences.ui.videoBwEstimate = hls.bandwidthEstimate; 76 - } 77 - }); 78 79 hls.on(Hls.Events.LEVEL_LOADED, (_event, data) => { 80 const hasAudio = data.levelInfo.audioCodec !== undefined;
··· 8 9 import { replaceVideoCdnUrl } from '~/lib/bsky/video'; 10 import { useSession } from '~/lib/states/session'; 11 + import { throttleTrailing } from '~/lib/utils/misc'; 12 13 const isMobile = /Android|iPhone|iPad|iPod/.test(navigator.userAgent); 14 ··· 22 23 const [playing, setPlaying] = createSignal(false); 24 25 + // const bwEstimate = currentAccount?.preferences.ui.videoBwEstimate; 26 + const bwEstimate = undefined; 27 const hls = new Hls({ 28 capLevelToPlayerSize: true, 29 ··· 72 node.volume = currentAccount.preferences.ui.mediaVolume; 73 } 74 75 + hls.on( 76 + Hls.Events.FRAG_LOADED, 77 + throttleTrailing(() => { 78 + if (currentAccount && !Number.isNaN(hls.bandwidthEstimate)) { 79 + currentAccount.preferences.ui.videoBwEstimate = 80 + Math.round(hls.bandwidthEstimate / 1_000_000) * 1_000_000; 81 + } 82 + }, 5_000), 83 + ); 84 85 hls.on(Hls.Events.LEVEL_LOADED, (_event, data) => { 86 const hasAudio = data.levelInfo.audioCodec !== undefined;
+5 -1
src/components/rich-text.tsx
··· 4 import type { AppBskyRichtextFacet } from '@atcute/bluesky'; 5 import { segmentize } from '@atcute/bluesky-richtext-segmenter'; 6 7 - import { isLinkValid } from '~/api/utils/strings'; 8 9 import { getCdnUrl } from '~/lib/bluemoji/render'; 10 import { redirectBskyUrl } from '~/lib/redirector'; ··· 46 47 if (type === 'app.bsky.richtext.facet#link') { 48 const uri = feature.uri; 49 const redirect = redirectBskyUrl(uri); 50 51 if (redirect == null) {
··· 4 import type { AppBskyRichtextFacet } from '@atcute/bluesky'; 5 import { segmentize } from '@atcute/bluesky-richtext-segmenter'; 6 7 + import { isLinkValid, safeUrlParse } from '~/api/utils/strings'; 8 9 import { getCdnUrl } from '~/lib/bluemoji/render'; 10 import { redirectBskyUrl } from '~/lib/redirector'; ··· 46 47 if (type === 'app.bsky.richtext.facet#link') { 48 const uri = feature.uri; 49 + if (safeUrlParse(uri) === null) { 50 + break; 51 + } 52 + 53 const redirect = redirectBskyUrl(uri); 54 55 if (redirect == null) {
+69
src/lib/utils/misc.ts
··· 88 89 return result as Omit<T, K>; 90 };
··· 88 89 return result as Omit<T, K>; 90 }; 91 + 92 + export const throttleLeading = <T extends (...args: any[]) => void>( 93 + fn: T, 94 + wait: number, 95 + ): ((...args: Parameters<T>) => void) => { 96 + let lastCallTime: number | undefined; 97 + 98 + return (...args: Parameters<T>) => { 99 + const now = performance.now(); 100 + 101 + if (lastCallTime === undefined || now - lastCallTime >= wait) { 102 + lastCallTime = now; 103 + fn(...args); 104 + } 105 + }; 106 + }; 107 + 108 + export const throttleTrailing = <T extends (...args: any[]) => void>( 109 + fn: T, 110 + wait: number, 111 + ): ((...args: Parameters<T>) => void) => { 112 + let timeoutId: ReturnType<typeof setTimeout> | undefined; 113 + let lastArgs: Parameters<T> | undefined; 114 + 115 + return (...args: Parameters<T>) => { 116 + lastArgs = args; 117 + 118 + if (timeoutId === undefined) { 119 + timeoutId = setTimeout(() => { 120 + timeoutId = undefined; 121 + fn(...lastArgs!); 122 + }, wait); 123 + } 124 + }; 125 + }; 126 + 127 + export const throttle = <T extends (...args: any[]) => void>( 128 + fn: T, 129 + wait: number, 130 + ): ((...args: Parameters<T>) => void) => { 131 + let timeoutId: ReturnType<typeof setTimeout> | undefined; 132 + let lastArgs: Parameters<T> | undefined; 133 + let lastCallTime: number | undefined; 134 + 135 + return (...args: Parameters<T>) => { 136 + const now = performance.now(); 137 + const elapsed = lastCallTime !== undefined ? now - lastCallTime : wait; 138 + 139 + if (elapsed >= wait) { 140 + if (timeoutId !== undefined) { 141 + clearTimeout(timeoutId); 142 + timeoutId = undefined; 143 + } 144 + 145 + lastCallTime = now; 146 + fn(...args); 147 + } else { 148 + lastArgs = args; 149 + 150 + if (timeoutId === undefined) { 151 + timeoutId = setTimeout(() => { 152 + timeoutId = undefined; 153 + lastCallTime = performance.now(); 154 + fn(...lastArgs!); 155 + }, wait - elapsed); 156 + } 157 + } 158 + }; 159 + };