Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 12 additions & 4 deletions js/audio-service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {Platform} from 'react-native';
import TrackPlayer, {State, Event, Capability, IOSCategory} from 'react-native-track-player';
import {Platform, EmitterSubscription} from 'react-native';
import TrackPlayer, {State, Event, Capability} from 'react-native-track-player';
import BackgroundTimer, {IntervalId} from 'react-native-background-timer';
import {
genAutopause,
Expand All @@ -19,11 +19,17 @@ type CurrentPlaying = {

let currentlyPlaying: CurrentPlaying | null = null;
let updateInterval: IntervalId | null = null;
let audioServiceSubscriptions: any[] = [];
let audioServiceSubscriptions: EmitterSubscription[] = [];

// when we enqueue then skip, it acts like we skipped from track 1 to track n. suppress the event
let suppressTrackChange = false;

interface PlaybackTrackChangedEventParams {
track: string | null;
position: number;
nextTrack: string | null;
}

export const genEnqueueFile = async (
course: Course,
lesson: number,
Expand Down Expand Up @@ -119,6 +125,7 @@ export const genStopPlaying = async () => {
await TrackPlayer.destroy();
};


export default async () => {
audioServiceSubscriptions.forEach((s) => s.remove());

Expand Down Expand Up @@ -246,12 +253,13 @@ export default async () => {
},
),


// welcome! you've found it. the worst code in the codebase.
// I have a personal policy of including explicit blame whenever I write code I know someone will curse me for one day.
// contact me@timothyaveni.com with your complaints.
TrackPlayer.addEventListener(
Event.PlaybackTrackChanged,
async (params) => {
async (params: PlaybackTrackChangedEventParams) => {
const wasPlaying = currentlyPlaying;

if (params.track == null || wasPlaying === null) {
Expand Down
27 changes: 17 additions & 10 deletions js/components/Listen/Listen.react.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,29 @@ import {genProgressForLesson} from '../../persistence';
import {log} from '../../metrics';
import {useSetStatusBarStyle} from '../../hooks/useStatusBarStyle';

const Listen = (props: any) => {
interface Props {
route: {
params: {
course: Course;
lesson: number;
};
};
navigation: {
pop: () => void;
};
}

const Listen = (props: Props) => {
const {course, lesson} = props.route.params;

const [bottomSheetOpen, setBottomSheetOpen] = useState(false);

const playbackState = usePlaybackState();
const playing = playbackState === State.Playing;

// go back to the previous screen when the user stops
// the music from outside the app
useTrackPlayerEvents([Event.RemoteStop], () =>
props.navigation.pop(),
);
useTrackPlayerEvents([Event.RemoteStop], () => props.navigation.pop());

// adjust the status bar style according to the course colors,
// and the bottom sheet visibility
Expand All @@ -39,11 +50,7 @@ const Listen = (props: any) => {
((navBarLight ? 'dark' : 'light') + '-content') as StatusBarStyle,
navBarLight,
);
}, [
setStatusBarStyle,
bottomSheetOpen,
course,
]);
}, [setStatusBarStyle, bottomSheetOpen, course]);

// load & queue audio file, find the last heard offset, and start
// the lesson
Expand Down Expand Up @@ -114,7 +121,7 @@ const Listen = (props: any) => {
position: await TrackPlayer.getPosition(),
});

TrackPlayer.seekTo(Math.max(0, await TrackPlayer.getPosition() - 10));
TrackPlayer.seekTo(Math.max(0, (await TrackPlayer.getPosition()) - 10));
};

return (
Expand Down
13 changes: 12 additions & 1 deletion js/metrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,18 @@ import CourseData from './course-data';

const LOG_ENDPOINT = 'https://metrics.languagetransfer.org/log';

export const log = async (data: any): Promise<void> => {
interface Data {
action: string;
surface?: string;
lesson?: number;
event?: string;
course?: Course;
metadata_version?: number;
position?: number;
setting_value?: number;
}

export const log = async (data: Data): Promise<void> => {
const [permitted, user_token] = await Promise.all([
genPreferenceAllowDataCollection(),
genMetricsToken(),
Expand Down
45 changes: 28 additions & 17 deletions js/persistence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,17 @@ import {log} from './metrics';
// haven't gone to the effort of adding a mutex. mostly because I don't like
// the API for the most popular library.

enum Preference {
AUTO_DELETE_FINISHED = 'auto-delete-finished',
STREAM_QUALITY = 'stream-quality',
DOWNLOAD_QUALITY = 'download-quality',
DOWNLOAD_ONLY_ON_WIFI = 'download-only-on-wifi',
ALLOW_DATA_COLLECTION = 'allow-data-collection',
IS_FIRST_LOAD = 'is-first-load',
RATING_BUTTON_DISMISSED = 'rating-button-dismissed',
KILLSWITCH_COURSE_VERSION_V1 = 'killswitch-course-version-v1',
}

export interface Progress {
finished: boolean;
progress: number | null;
Expand Down Expand Up @@ -183,42 +194,42 @@ const preference = (
export const [
genPreferenceAutoDeleteFinished,
genSetPreferenceAutoDeleteFinished,
] = preference('auto-delete-finished', false, (b) => b === 'true');
] = preference(Preference.AUTO_DELETE_FINISHED, false, (b) => b === 'true');

export const [
genPreferenceStreamQuality,
genSetPreferenceStreamQuality,
] = preference('stream-quality', 'low', (b) => b);
export const [genPreferenceStreamQuality, genSetPreferenceStreamQuality] =
preference(Preference.STREAM_QUALITY, 'low', (b) => b);

export const [
genPreferenceDownloadQuality,
genSetPreferenceDownloadQuality,
] = preference('download-quality', 'high', (b) => b);
export const [genPreferenceDownloadQuality, genSetPreferenceDownloadQuality] =
preference(Preference.DOWNLOAD_QUALITY, 'high', (b) => b);

export const [
genPreferenceDownloadOnlyOnWifi,
genSetPreferenceDownloadOnlyOnWifi,
] = preference('download-only-on-wifi', true, (b) => b === 'true');
] = preference(Preference.DOWNLOAD_ONLY_ON_WIFI, true, (b) => b === 'true');

export const [
genPreferenceAllowDataCollection,
genSetPreferenceAllowDataCollection,
] = preference('allow-data-collection', true, (b) => b === 'true');
] = preference(Preference.ALLOW_DATA_COLLECTION, true, (b) => b === 'true');

export const [
genPreferenceIsFirstLoad,
genSetPreferenceIsFirstLoad,
] = preference('is-first-load', true, (b) => b === 'true');
export const [genPreferenceIsFirstLoad, genSetPreferenceIsFirstLoad] =
preference(Preference.IS_FIRST_LOAD, true, (b) => b === 'true');

export const [
genPreferenceRatingButtonDismissed,
genSetPreferenceRatingButtonDismissed,
] = preference('rating-button-dismissed', { dismissed: false }, (o) => JSON.parse(o));
] = preference(Preference.RATING_BUTTON_DISMISSED, {dismissed: false}, (o) =>
JSON.parse(o),
);

export const [
genPreferenceKillswitchCourseVersionV1,
genSetPreferenceKillswitchCourseVersionV1,
] = preference('killswitch-course-version-v1', false, (b) => b === 'true');
] = preference(
Preference.KILLSWITCH_COURSE_VERSION_V1,
false,
(b) => b === 'true',
);

export function usePreference<T>(key: Preference, defaultValue: any) {
const [value, setValue] = useState<T>(null!);
Expand Down