Skip to content
Draft
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
97 changes: 88 additions & 9 deletions mobile-app/lib/models/learn/challenge_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ class Challenge {

// English Challenges
final FillInTheBlank? fillInTheBlank;
final EnglishAudio? audio;
final EnglishScene? audio;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm adding a new QuizAudio class so I renamed EnglishAudio to EnglishScene, to better differentiate the two.

final Scene? scene;

// Nodules for interactive challenges
Expand Down Expand Up @@ -125,7 +125,7 @@ class Challenge {
? FillInTheBlank.fromJson(data['fillInTheBlank'])
: null,
audio: data['scene'] != null
? EnglishAudio.fromJson(data['scene']['setup']['audio'])
? EnglishScene.fromJson(data['scene']['setup']['audio'])
: null,
tests: (data['tests'] ?? [])
.map<ChallengeTest>((file) => ChallengeTest.fromJson(file))
Expand Down Expand Up @@ -456,11 +456,13 @@ class QuizQuestion {
final String text;
final List<Answer> answers;
final int solution;
final QuizAudioData? audioData;

const QuizQuestion({
required this.text,
required this.answers,
required this.solution,
this.audioData,
});

factory QuizQuestion.fromJson(Map<String, dynamic> data) {
Expand All @@ -482,7 +484,13 @@ class QuizQuestion {
allAnswers.indexWhere((a) => a.answer == data['answer']) + 1;

return QuizQuestion(
text: data['text'], answers: allAnswers, solution: solutionIndex);
text: data['text'],
answers: allAnswers,
solution: solutionIndex,
audioData: data['audioData'] != null
? QuizAudioData.fromJson(data['audioData'])
: null,
);
}
}

Expand Down Expand Up @@ -526,7 +534,7 @@ class Scene {
class SceneSetup {
final String background;
final bool? alwaysShowDialogue;
final EnglishAudio audio;
final EnglishScene audio;
final List<SceneCharacter> characters;

const SceneSetup({
Expand All @@ -539,7 +547,7 @@ class SceneSetup {
factory SceneSetup.fromJson(Map<String, dynamic> data) {
return SceneSetup(
background: data['background'],
audio: EnglishAudio.fromJson(data['audio']),
audio: EnglishScene.fromJson(data['audio']),
characters: data['characters']
.map<SceneCharacter>(
(character) => SceneCharacter.fromJson(character),
Expand Down Expand Up @@ -642,25 +650,96 @@ class SceneDialogue {
}
}

class EnglishAudio {
abstract class AudioClip {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implemented a shared class so both scene and quiz audio can share audio logic. (I did try to make the scene and quiz audio schema as close as possible for this purpose).

String get fileName;
String? get startTimeStamp;
String? get finishTimeStamp;
}

class EnglishScene implements AudioClip {
@override
final String fileName;
final String startTime;
@override
final String? startTimeStamp;
@override
final String? finishTimeStamp;

const EnglishAudio({
const EnglishScene({
required this.fileName,
required this.startTime,
required this.startTimeStamp,
required this.finishTimeStamp,
});

factory EnglishAudio.fromJson(Map<String, dynamic> data) {
return EnglishAudio(
factory EnglishScene.fromJson(Map<String, dynamic> data) {
return EnglishScene(
fileName: data['filename'],
startTime: data['startTime'].toString(),
startTimeStamp: data['startTimestamp']?.toString(),
finishTimeStamp: data['finishTimestamp']?.toString(),
);
}
}

class QuizTranscriptLine {
final String character;
final String text;

const QuizTranscriptLine({
required this.character,
required this.text,
});

factory QuizTranscriptLine.fromJson(Map<String, dynamic> data) {
return QuizTranscriptLine(
character: data['character'],
text: data['text'],
);
}
}

class QuizAudio implements AudioClip {
@override
final String fileName;
@override
final String? startTimeStamp;
@override
final String? finishTimeStamp;

const QuizAudio({
required this.fileName,
this.startTimeStamp,
this.finishTimeStamp,
});

factory QuizAudio.fromJson(Map<String, dynamic> data) {
return QuizAudio(
fileName: data['filename'],
startTimeStamp: data['startTimestamp']?.toString(),
finishTimeStamp: data['finishTimestamp']?.toString(),
);
}
}

class QuizAudioData {
final QuizAudio audio;
final List<QuizTranscriptLine> transcript;

const QuizAudioData({
required this.audio,
required this.transcript,
});

factory QuizAudioData.fromJson(Map<String, dynamic> data) {
final audioData = data['audio'];
return QuizAudioData(
audio: QuizAudio.fromJson(audioData),
transcript: (data['transcript'] as List)
.map<QuizTranscriptLine>(
(item) => QuizTranscriptLine.fromJson(item),
)
.toList(),
);
}
}
10 changes: 4 additions & 6 deletions mobile-app/lib/service/audio/audio_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ class AudioPlayerHandler extends BaseAudioHandler {
return 'https://cdn.freecodecamp.org/curriculum/english/animation-assets/sounds/$fileName';
}

bool canSeek(bool forward, int currentDuration, EnglishAudio audio) {
bool canSeek(bool forward, int currentDuration, AudioClip audio) {
currentDuration =
currentDuration + parseTimeStamp(audio.startTimeStamp).inSeconds;

Expand All @@ -252,23 +252,21 @@ class AudioPlayerHandler extends BaseAudioHandler {
}
}

void loadEnglishAudio(EnglishAudio audio) async {
Future<void> loadCurriculumAudio(AudioClip audio) async {
await _audioPlayer.setAudioSource(
ClippingAudioSource(
start: parseTimeStamp(audio.startTimeStamp),
end: audio.finishTimeStamp == null
? null
: parseTimeStamp(audio.finishTimeStamp),
child: AudioSource.uri(
Uri.parse(
returnUrl(audio.fileName),
),
Uri.parse(returnUrl(audio.fileName)),
),
),
);
await _audioPlayer.load();
setEpisodeId = '';
_audioType = 'english';
_audioType = 'curriculum';
}

void _notifyAudioHandlerAboutPlaybackEvents() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class QuizViewModel extends BaseViewModel {
text: q.text,
answers: q.answers,
solution: q.solution,
audioData: q.audioData,
))
.toList();
}
Expand Down Expand Up @@ -114,6 +115,7 @@ class QuizViewModel extends BaseViewModel {
text: q.text,
answers: q.answers,
solution: q.solution,
audioData: q.audioData,
))
.toList();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ import 'package:stacked/stacked.dart';
class AudioPlayerView extends StatelessWidget {
const AudioPlayerView({super.key, required this.audio});

final EnglishAudio audio;
final EnglishScene audio;

@override
Widget build(BuildContext context) {
return ViewModelBuilder<AudioPlayerViewmodel>.reactive(
viewModelBuilder: () => AudioPlayerViewmodel(),
onViewModelReady: (model) => {
model.initPositionListener(),
model.audioService.loadEnglishAudio(audio)
model.audioService.loadCurriculumAudio(audio)
},
onDispose: (model) => model.onDispose(),
builder: (context, model, child) => Padding(
Expand Down Expand Up @@ -48,7 +48,7 @@ class InnerAudioWidget extends StatelessWidget {
});

final AudioPlayerViewmodel model;
final EnglishAudio audio;
final EnglishScene audio;
final PlaybackState playerState;

@override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class AudioPlayerViewmodel extends BaseViewModel {
Duration searchTimeStamp(
bool forwards,
int currentPosition,
EnglishAudio audio,
EnglishScene audio,
) {
if (forwards) {
return Duration(
Expand Down
Loading
Loading