Меня давно раздражали лаги в стримингах и постоянные редизайны, когда ты не понимаешь, как теперь всё работает, а иногда оно и не работает вовсе. Отдельно раздражает, когда заходишь в лифт, а «скачанный» трек почему-то не включается. Ну и отсутствие каких-либо треков или их удаление из твоего же плейлиста. Я устал собирать годами плейлисты, чтобы потом они оказались выкошены под ноль. Мне не нужны концерты и фотки артистов, криво прилепленные к карточкам. Не нужны нейрослоп и реклама даже в платном продукте. Я просто хочу слушать музыку.

Когда к этому добавились ещё и правки самих треков, я сдался. Решил пересесть на локальный плеер, который у меня никто не заберёт. Дальше я полез искать что-то, что работает одинаково на всех платформах и может воспроизводить разные форматы. Что-то простое и лёгкое, чтобы я просто мог включить и слушать.

После ресёрча нашёл массу отличных проектов, начиная со старого доброго VLC и заканчивая чем-то совсем экзотическим. Но из того, что попробовал, подходящего плеера на все платформы не нашлось. Я подумал: ладно, ничего страшного, можно попробовать разные клиенты, треки же одни и те же. И вот тут возникли сложности.

Чего мне не хватило в существующих плеерах?

Я попользовался VLC, плеер старый, но крутой. Однако мне показалось очень неудобным пополнение плейлистов. Ещё возникла проблема: что, если хочется иметь одну медиатеку на всех платформах и поддерживать её в согласованном состоянии? Файлы можно перекинуть, а с плейлистами и порядком треков у разных клиентов уже свои правила. Тут и появилась первая причина, почему я отказался от идеи «разные плееры под разные платформы».

Мне важно, чтобы плеер работал быстро и он имел одинаковый UI/UX на компьютере и телефоне. Плюс не было визуального шума и привязки к интернету, потому что часто его может в принципе не быть.

Ещё тогда висел вопрос, как синхронизировать устройства на разных платформах. Облачные хранилища - это удобно, просто и почти всегда доступно, но медиатека, особенно в lossless, может занимать сотни гигабайт. Хранить её целиком в облаке кажется не лучшим выбором.

В итоге готового решения под свой юзкейс я не нашел и решил разработать своё. Звучит не так тяжело, всего лишь плеер. После этого я решил формализовать что я хочу:

  • Полностью локальная музыка, проигрываемая из папки.

  • Кроссплатформенность.

  • Лёгкий бэкап.

  • Быстрый UI без шума.

  • Быстрый поиск.

  • Синхронизация между устройствами.

В эру вайбкода

Я вообще не переживал: введу один промпт, и GPT всё сделает за меня. Как же я ошибался…

Первые прототипы действительно были собраны очень быстро, но, к сожалению, с работоспособностью у них было мало общего. Первую версию я попытался написать на React Native. Выбор был таким, потому что я работал с React и экосистемой TypeScript и почему-то думал, что это отличная идея. Однако когда речь зашла о запуске треков из файловой системы, всё посыпалось.

Мне не хватило опыта в нативной разработке, чтобы с этим разобраться. Я согласен что я плохой танцор, которому React Native помешал.

Dart и Flutter мне сразу понравились: только попробовал поделать какую-то ерунду и у меня уже всё работало. Всё запускалось, не было кучи варнингов и прочего, что отбивает интерес к изучению чего-то нового. Я подумал, что эта платформа позволит реализовать мою задумку, и начал изучать и пробовать.

Что я выбрал и почему

В этот раз я решил подойти серьёзнее, чем в прошлой итерации, и разобраться, что используют люди и что подойдёт моему приложению.

Первое, что выбрал, - БД. Хотелось максимально быстрое приложение, и я подумал, может, наивно, что лучше бы мне бдшку побыстрее. Сложных запросов я тогда не предвидел: на первый взгляд не очень ясно, где они пригодятся при хранении треков. В итоге выбрал Isar. Понравились работа с объектами из Dart и результаты бенчмарков. Своего сравнения с SQLite я не проводил, так что «быстрее всех» тут утверждать не буду.

Для управления состоянием взял Riverpod. Он показался удобным: отвязывание логики от экранов это то что мне было нужно, так же как и работать с асинхронными данными. А вот с тем, как быстро сами экраны отрисовываются, пришлось разбираться уже отдельно.

Разработка

Я подумал, что этих двух вещей будет достаточно и я смогу решить поставленную задачу на Flutter. Сделал первый прототип, и, о чудо, он работал. Действительно играл простенький MP3.

Но когда в библиотеке стало больше 100 файлов, запуск оказался ужасно медленным. Причина на самом деле то простая: сканирование папки, чтение тегов - и всё это каждый раз при запуске. Причём чем больше папка, тем дольше ждать, что тоже ни в какие ворота не лезет.

Чтобы не сканировать папку и не читать теги заново при каждом запуске, я подключил БД и стал сохранять в ней данные о треках. Теперь можно было отрисовывать пользователю уже закэшированный список. После этого наплодил кучу багов из разряда «а что, если трек удалили в обход приложения?». Запись есть, кнопка Play есть, а воспроизводить уже нечего. А если трек добавили? Надо как-то просканировать папку. А если просто переименовали? Я не понимал, как поддерживать согласованное состояние, чтобы оно ещё и быстро работало.

Долго думая, я понял, что нужно отойти от идеи «открываем любую папку и следим за ней». Вдохновившись Obsidian, я захотел сделать такие же «хранилища» и располагать БД внутри каждого из них. В этот же момент вспомнил о другом своём требовании: синхронизация должна быть простой. Идея перекинуть всё вместе с данными приложения и просто открыть на другом устройстве показалась мне перспективной.

Так же я подумал о проблеме расположения треков. Когда мы используем стриминги, обычно “последний” трек который мы добавили - вверху, однако не ясно по какому принципу мне определять его новизну в локальном плеере.

В итоге пришел к тому что отвязал скан от интерфейса и стал показывать сохранённое состояние из БД, пока сканируется папка. Повторная проверка запускается раз в несколько секунд, найденные изменения сохраняются в БД. Если предыдущий скан ещё работает, второй поверх него не стартует.

Как выглядит запись о треке
@collection
class Track {
  Id id = Isar.autoIncrement;

  @Index(unique: true)
  late String trackUid;

  @Index()
  late String contentSha256;

  @Index()
  late String path;

  late String title;
  String? artist;
  String? artworkPath;

  late DateTime addedAt;
  late DateTime modifiedAt;

  late int size;
  String? format;
  int? durationMs;
}

Если при следующем скане находятся новые треки, в addedAt ставится время их обнаружения. Эти треки перемещаются наверх. При обычном обновлении записи время добавления не меняется.

После этого попробовал открыть большую библиотеку и увидел ещё одну проблему: UI долго рендерится. Добавил постепенную подгрузку списка: сначала доступны первые 15 треков, потом ещё по 60 при прокрутке. Строки строятся через ListView.builder. Это пока только про интерфейс, из БД список всё ещё загружается целиком.

Заодно добавил поиск по названию и артисту. Кстати, думаю, многие встречались с тем, что в имени исполнителя какая-то странная буква или символ и трек не ищется. Поэтому текст перед поиском нормализуется, а сам поиск допускает одну вставку, удаление или замену символа. Он работает в отдельном isolate и ищет ещё и по плейлистам. При использовании оказалось довольно удобно.

Сами плейлисты ссылаются на треки из БД. Повторные ссылки на один и тот же трек убираются по UID или хэшу файла. С хэшем дальше обнаружился нюанс, о нём в синхронизации.

Ещё я подумал, что может, это и не совсем надёжно, но, по большому счёту, если в папке ничего не изменилось, зачем заново читать все теги?

То есть я получаю список путей в хранилище, к ним приджойниваю размер файла и дату последнего изменения, сортирую по пути и беру хэш от всего списка. Это fingerprint хранилища. Если он совпал с предыдущим, повторное чтение тегов можно пропустить.

Процесс выглядит так:

  1. Обходим папку и собираем список файлов.

  2. Для каждого файла берём путь, размер и дату изменения.

  3. Из этого списка считаем fingerprint хранилища.

  4. Сравниваем его с предыдущим fingerprint.

  5. Если fingerprint изменился - обновляем индекс.

Правда, саму файловую систему для этого всё равно приходится обходить. Я избавился от повторного чтения тегов при неизменном состоянии, а не от обхода папки. И если fingerprint изменился, текущая реализация перечитывает теги всех найденных треков. Хэш содержимого пересчитывается только для новых файлов, записей без хэша и файлов с изменившимися размером или временем модификации.

Код обхода папки и расчёта fingerprint
import 'dart:convert';
import 'dart:io';
import 'dart:isolate';

import 'package:crypto/crypto.dart';
import 'package:path/path.dart' as p;

class FileMeta {
  FileMeta({required this.path, required this.size, required this.modifiedMs});

  final String path;
  final int size;
  final int modifiedMs;
}

const supportedExtensions = <String>{
  '.mp3',
  '.m4a',
  '.flac',
  '.ogg',
  '.wav',
  '.aiff',
  '.alac',
  '.aac',
};

Future<List<FileMeta>> listAudioFiles(String rootPath) async {
  return Isolate.run(() => _listAudioFilesSync(rootPath));
}

Future<String> contentSha256ForFile(String path) {
  return Isolate.run(() async {
    final digest = await sha256.bind(File(path).openRead()).first;
    return digest.toString();
  });
}

List<FileMeta> _listAudioFilesSync(String rootPath) {
  final root = Directory(rootPath);
  if (!root.existsSync()) return [];
  final metas = <FileMeta>[];
  for (final entity in root.listSync(recursive: true, followLinks: false)) {
    if (entity is! File) continue;
    final path = entity.path;
    if (path.contains('${p.separator}thyron${p.separator}')) {
      continue;
    }
    final ext = p.extension(path).toLowerCase();
    if (!supportedExtensions.contains(ext)) continue;
    final stat = entity.statSync();
    metas.add(
      FileMeta(
        path: path,
        size: stat.size,
        modifiedMs: stat.modified.millisecondsSinceEpoch,
      ),
    );
  }
  metas.sort((a, b) => a.path.compareTo(b.path));
  return metas;
}

const _fingerprintVersion = 6;

String fingerprintForFiles(List<FileMeta> metas) {
  final buffer = StringBuffer();
  buffer.writeln('v$_fingerprintVersion');
  for (final meta in metas) {
    buffer
      ..write(meta.path)
      ..write('|')
      ..write(meta.size)
      ..write('|')
      ..write(meta.modifiedMs)
      ..write('\n');
  }
  return sha1.convert(utf8.encode(buffer.toString())).toString();
}

Версия в fingerprint позволяет после изменения логики чтения тегов один раз переиндексировать старое хранилище.

С удалениями стало понятнее: при следующем скане отсутствующая запись убирается из БД и плейлистов. До этого момента она ещё может быть видна. Переименование я пока определяю по совпадению размера и времени изменения с записью, у которой исчез старый путь. Это может ошибиться на разных файлах с одинаковыми атрибутами. Ну и если кто-то поменяет файл, сохранив размер и дату модификации, такая проверка этого не заметит.

Даже если в хранилище ничего не изменилось, список файлов всё равно нужно собрать. Вот сколько занимают обход папки и расчёт fingerprint при разном количестве файлов:

Файлов

Обход папки и расчёт fingerprint

100

0,64 мс

1 000

5,35 мс

10 000

63,17 мс

Это работа, которая остаётся даже при неизменной медиатеке. К сожалению я не смог пока что придумать способ который позволит избежать постоянного рескана, если есть идеи напишите.

Влияние сканирования на мобильные устройства я пока не измерял. Допускаю, что на большой медиатеке оно может быть существенным и приложение из-за постоянных проверок может есть зарядку.

Условия замера

MacBook Pro, Apple M4 Pro, 48 ГБ памяти, macOS 15.6, Dart 3.10.7, AOT-сборка. Измерялись listAudioFiles и fingerprintForFiles из кода приложения, обычным локальным запуском, без файловой песочницы.

Данные синтетические: WAV по одной секунде, 88 244 байта каждый, по 100 файлов в подпапке. В таблице медианы девяти проверок неизменной папки после трёх прогревочных обходов. Создание файлов в измерение не входит, кэш файловой системы не сбрасывался. Для холодного запуска приложения и реальной медиатеки с тегами нужны отдельные измерения.

После этого начальная версия приложения была готова. Выглядело это как-то так…

Ранняя версия Thyron на Android, iOS и macOS
Ранняя версия Thyron на Android, iOS и macOS

Первая рабочая версия с тестовыми треками.

Синхронизация

После того как просто проигрывалка была готова, нужно было сделать удобный механизм синхронизации. Конечно, есть вариант перекидывать всё по проводу, но для этого нужен провод. Представим обычный сценарий: появился новый трек, добавили его в папочку, теперь нужно перенести его на остальные устройства. И желательно не отправлять заново то, что там уже есть.

Я решил не использовать облако, раз уж делаю local-first приложение. Передача будет напрямую в локальной сети. Одно приложение на всех платформах позволяет одинаково хранить треки и их метаданные, а значит, можно сделать общий протокол передачи треков между устройствами.

В текущей реализации пути к трекам сохраняются относительно корня хранилища. В служебной папке thyron лежат БД, обложки и manifest.json с описанием треков и плейлистов. С манифестом переносится и время добавления треков. На десктопе и Android приложение работает с выбранной папкой, а на iOS музыка импортируется во внутреннее хранилище приложения.

Тут можно спросить, почему не взять обычный плеер и Syncthing. Для копирования папок это нормальный вариант. Но мне ещё хотелось одинаково работать с плейлистами и отправлять выбранную музыку прямо из приложения. Чтобы выбрал плейлист, нажал передать - и он появился в другом плеере вместе с треками.

Работает так: на одном устройстве включается «приём», на втором - «передача». Получатель объявляет себя в локальной сети через mDNS/DNS-SD (для этого использую Bonsoir). Отправитель находит его и по HTTP передаёт манифест с описанием треков и плейлистов. Получатель сверяет хэши файлов со своими и отвечает, чего не хватает. Дальше передаются только недостающие файлы.

Локальный id из БД для этого не подходит: на двух устройствах он может быть разным. Поэтому у трека есть ещё переносимый trackUid, а отдельно - contentSha256, хэш всех байтов файла. По нему и проверяется, есть ли уже такая копия у получателя.

Кстати, вот тот нюанс с дедупликацией. Если поправить тег или обложку, хэш изменится, хотя сама музыка осталась прежней. Два разных кодирования одной песни тоже дадут разные хэши. Я убираю повторы одинаковых файлов при передаче, а не распознаю одинаковое аудио. Это ограничение, но мне такой подход кажется понятным. Если человек хранит одну песню во flac и mp3 или две копии с разными тегами, у него могут быть на это причины. Не хочется, чтобы плеер сам решал, какая из них лишняя.

При редактировании тегов trackUid сохраняется, а хэш обновляется. Если потом передать этот трек, на втором устройстве может остаться старая копия: существующий файл я не перезаписываю. При совпадении имён новая версия получает суффикс, общий UID переходит к ней, а старой записи выдаётся новый. Ссылки в плейлистах перепривязываются к полученной версии.

Я выбрал стратегию слияния медиатек: если пользователь на одном устройстве удалил трек, а на втором оставил, то при передаче со второго устройства этот трек скинется заново. Это, кажется, не лучший сценарий, но я не хотел возиться с флоу выбора главного устройства. К тому же проще удалить трек на двух устройствах, чем глазами сверять медиатеки и выбирать, что оставить, чтобы при синхронизации не удалить нужное.

С плейлистами оказалось не совсем «просто передать ссылки». Они сопоставляются по UID, а если совпадения нет - по названию без учёта регистра и лишних пробелов. Недостающие треки добавляются, повторные ссылки убираются. Для названия берётся более поздний updatedAt. То есть одинаково названные плейлисты могут объединиться, часы устройств влияют на выбор названия, а удалённый из плейлиста трек при следующем merge может вернуться. Тут собственно и оправдывается выбор этой стратегии, потому что как сшивать плейлисты при выборе главного тоже не ясно.

А если передача оборвётся?

Каждый файл сначала записывается во временный .part. Затем проверяются размер и SHA-256, и только после этого он перемещается в медиатеку. Недокачанный файл готовым треком не считается.

Докачки с конкретного байта пока нет, оборвавшийся файл придётся отправить заново. Полностью принятые файлы уже остаются на диске, при новом сеансе получатель пересканирует папку и учтёт их. Сам манифест применяется после получения всех необходимых файлов — так что передача целиком не является одной атомарной операцией.

Ещё сейчас есть ограничение: это передача для доверенной локальной сети. Токен создаётся заново на каждый сеанс и передаётся в HTTP-заголовке, но объявляется вместе с сервисом в той же сети. TLS и отдельного подтверждения устройств по ключам пока нет.

Я задумываюсь над сценарием «состояние задаёт лидирующее устройство», то есть то, которое начинает передачу, но пока не знаю, кажется, это будет иметь проблемы с согласованностью и риски потери данных. Но тогда можно было бы переносить и удаления файлов.

Билды, тесты и деплои

Когда ты единственный разработчик казалось бы простого приложения, но на пяти платформах, возникает очень много сложностей с тестированием. Первые версии я тестировал вручную. Как следствие - очень много времени и довольно низкое качество: постоянно приходилось проверять новые билды, и казалось, этот круг не закончится.

Основные проблемы были с Windows и Linux. На Linux у меня так и не получилось сделать нормальный мини-плеер, все попытки оказались нерабочими. В итоге там вместо мини-плеера обычная выпадашка в трее.

А Windows просто не хотел играть FLAC. Сначала я использовал just_audio_windows, но в итоге заменил его на just_audio_media_kit и подключил media_kit_libs_windows_audio. Со стороны Dart остался привычный API just_audio, а за воспроизведение теперь отвечала libmpv, которая поставлялась вместе с приложением.

С переключением треков тоже вышло интересно. Пользователь выбирает трек, я записываю его номер в очереди как текущий и только потом отправляю движку команду переключиться. Если возникала ошибка, я её ловил и останавливал воспроизведение, но код, который обновлял интерфейс, об этом не узнавал. Он всё равно показывал выбранный трек как играющий. То есть сам факт выбора трека у меня превратился в подтверждение того, что он заиграл.

Для Windows я в итоге перестал передавать движку всю очередь. Теперь приложение само выбирало нужный файл, приостанавливало воспроизведение и открывало этот файл в движке. Когда трек заканчивался, приложение таким же образом открывало следующий. Заодно стал передавать результат операции в код обновления интерфейса: если произошла ошибка, показывать успешное переключение уже не надо.

В общем спустя несколько недель таких тестировочных упражнений я всё же довёл приложение до состояния, которым можно было пользоваться.

Сейчас в проекте уже есть unit- и интеграционные тесты на сканирование, синхронизацию, плейлисты, теги и восстановление после перезапуска. Но начиналось всё именно с ручного перебора платформ. Поделитесь со мной опытом, как можно дёшево реализовать хорошее e2e тестирование на всех платформах. Я пока не знаю, надо думать дальше.

Первые шаги

После того как зарелизил приложение, я полностью пересел на него. Оно меня устраивало: да, выглядело простенько, но зато работало везде. Треки добавлялись, я мог спокойно слушать музыку на тренировке, в метро и где угодно. Подумал, что отложу проект в долгий ящик, да и пусть существует. Ноо… дни шли, фичи в голове копились, да и дизайн из 2010-го надоел.

Ещё очень бесило, что у треков разная громкость. Особенно рабочий плейлист: постоянно то тише, то громче. Плюс к этому хотелось сделать возможным не привязываться к собственному хранилищу при передаче трека. Условно, если я сижу с другом, попивая пиво, захочу послушать Вивальди, а ему понравится, то я не могу скинуть ему этот трек. Также не могу поделиться всеми изысками классического искусства, объединёнными в плейлист.

Версия 2.0

Я решил доработать существующее приложение, и начал с дизайна. Теперь приложение выглядит вот так:

Thyron 2.0: треки и плейлисты на одном экране
Thyron 2.0: треки и плейлисты на одном экране

Снимок из iPad-симулятора с тестовой медиатекой. Треки и плейлисты теперь рядом, а на телефоне между ними можно переключаться.

После этого добавил выравнивание громкости. В текущей версии трек анализируется в фоне, результат сохраняется в небольшом кэше, а при воспроизведении применяется постоянное усиление для этого трека. Исходный файл не меняется. Если анализ ещё не готов, музыка играет с исходной громкостью, а результат используется при следующем открытии трека.

Переработал флоу передачи файлов. Если ничего не выбрано, передача работает как раньше. Если выбраны треки или плейлист, передаются только они, вместе с обложкой плейлиста. Другому устройству достаточно нажать «принять», и всё передастся внутри локальной сети.

Также заметил, что далеко не все аудиофайлы обладают хорошими тегами автора и названия. Фолбэк у меня, конечно, был: если название не читается, ставится имя файла. Но это не то. Решил добавить редактор тегов, чтобы прямо с телефона можно было поправить название, исполнителя или обложку. Вот тут уже файл изменяется, тэги пишутся прям в него.

Редактор названия, исполнителя и обложки в Thyron 2.0
Редактор названия, исполнителя и обложки в Thyron 2.0

Снимок из iPhone-симулятора. После сохранения приложение обновляет запись о треке и сохраняет его связи с плейлистами.

Ну и добавил аналог VIM-навигации на десктопе: Cmd/Ctrl + H, J, K, L, чтобы перемещаться между треками и плейлистами с клавиатуры. Раз уж делаю плеер для себя, почему бы и нет. Так же, если выбрать просто окно приложения то можно не нажимать на поиск а сразу начинать печатать, приложение само подхватит.

Что получилось

Сейчас Thyron работает на Windows, Linux, macOS, Android и iOS. Я пользуюсь им сам, и по большому счёту он решает то, ради чего я его сделал: музыка лежит на моих устройствах, играет без интернета и передаётся между ними без облака.

Конечно, всё ещё есть что доделывать. Удаления между устройствами не переносятся, после правки тегов может появиться дополнительная копия, папку приходится периодически обходить. Ну и резервную копию музыки лучше всё-таки иметь отдельно: мой плеер её за пользователя не делает.

Времени ушло гораздо больше, чем я представлял. Причём музыка заиграла довольно быстро, а потом начались файлы, которые кто-то переименовал, плейлисты, которые потеряли ссылки, и очередной билд, который на одной платформе работает, а на другой нет. А начиналось всё с мысли «всего лишь плеер».

Посмотреть результат можно на thyron.ru / thyron.org там есть демо интерфейса и сборки.

Комментарии (11)


  1. webwork
    20.10.2026 06:49

    Здравствуйте. А подскажите, пожалуйста, можно ли в ваш плеер встроить ускроение например x4 и вместе с треком держать индекс и по нему мотать, как на youtube сделано.

    У меня проблема с аудиокнигами - я их озвучиваю через нейронку, а слушать могу на разных устройствах. На скорости 1 - это очень медленно. А перематывать по индексу (например, по главам) - это если я прочитал на книге, чтобы перейти сразу на главу.



  1. MrCleaner
    20.10.2026 06:49

    Сыро. Под Windows вешает в трей столько процессов, сколько раз запущенно приложение. Под Android в упор не видит mp3.


    1. E-Bor Автор
      20.10.2026 06:49

      Спасибо за обратную связь! С Windows разберусь, поправлю. По Android можете, пожалуйста, указать версию ОС и модель устройства вот здесь: https://github.com/E-Bor/thyron-feedback/issues

      Это поможет быстрее воспроизвести проблему.


  1. Traveller0968
    20.10.2026 06:49

    Ну первая претензия!
    Почему нельзя добавить всю папку целиком из другой дериктории?


    1. E-Bor Автор
      20.10.2026 06:49

      Сейчас можно открыть любую директорию как отдельное хранилище. Если хотите добавить папку в уже существующее хранилище, её можно просто скопировать через файловую систему. Или вы хотите какой то другой сценарий?


      1. Traveller0968
        20.10.2026 06:49

        Скопировать здесь ключевое слово, т.е. у меня допустим 10 гигов, после копирования будет 20, зачем лишним засирать память телефона, просто обозначить папку где это лежит, пусть к ней и обращается, это же очевидно и да, я уже перепробовал туеву хучу всяких плееров и ни один, даже тот который способен играть OffLine, меня не устраивает, все они в той или иной степени ущербны в.т.ч. и этот пресловутый VLC, хотя из всех зол, он мне кажется наименьшим, и то это тоже спорно...
        Я говорю про плееры которые можно установить на iPhone


        1. E-Bor Автор
          20.10.2026 06:49

          Понял ваш сценарий. На данный момент Thyron для iOS не поддерживает подключение внешних папок, но такой функционал планируется добавить.

          Пока могу предложить следующий вариант: импортировать музыку в Thyron, убедиться, что всё успешно, и удалить исходную папку. Таким образом, не придётся постоянно хранить две копии медиатеки.

          Только предварительно рекомендую сделать резервную копию музыки, например на ПК, поскольку при удалении Thyron iOS удалит и все файлы, хранящиеся внутри приложения.