O player de áudio que eu mesmo fiz
(nexo.sh)- Partindo do problema de que, mesmo em 2025, ainda é difícil ouvir e sincronizar livremente arquivos de música que o usuário possui no iPhone, foi implementado diretamente em SwiftUI um player de música local-first
- Ao cancelar a assinatura do Apple Music, a sincronização da iCloud Music Library para, e o iTunes Match é uma opção paga separada de US$ 24,99/ano para armazenar cópias AAC de 256 kbps online
- React Native/Expo falhava com frequência ou causava crashes ao percorrer pastas do iCloud profundamente aninhadas e lidar com permissões de arquivo, então uma implementação em SwiftUI usando APIs nativas da Apple acabou sendo a escolha mais simples
- O app usa FTS5 do SQLite para pesquisar nome de arquivo e metadados como artist, album e title, e foi organizado separando importação do iCloud, gerenciamento da biblioteca e tela de reprodução
- No iOS, por causa de bookmarks com escopo de segurança e das limitações de builds pessoais de desenvolvedor, ainda existem barreiras de acesso a arquivos e distribuição mesmo para usar por muito tempo um app feito pela própria pessoa
Por que criar um player de música do zero
- No iPhone, ainda existe a situação em que, para reproduzir arquivos de música que o usuário já possui, é preciso pagar para a Apple ou contornar várias limitações
- Para usuários que já organizaram sua biblioteca de música local, serviços de streaming são mais um recurso de conveniência do que uma necessidade
- Ao cancelar a assinatura do Apple Music, a sincronização entre dispositivos baseada na iCloud Music Library é interrompida
- Esse recurso fica atrás da assinatura
- Com o iTunes Match, dá para voltar a usá-lo por US$ 24,99/ano
- O Match armazena cópias AAC de 256 kbps online, enquanto os arquivos originais permanecem inalterados, a menos que o usuário os modifique
- Sem assinatura, a sincronização em nuvem desaparece e é preciso voltar à sincronização por cabo/Wi‑Fi
- O objetivo era criar diretamente um player de música básico que importasse, organizasse e reproduzisse arquivos de áudio
- O app final oferece busca por texto completo, suporte a iCloud e uma experiência local-first, e foi publicado no GitHub
Por que as opções existentes não serviam
-
Apps padrão da Apple
- Dá para reproduzir diretamente arquivos de música do iCloud pelo app Files, mas ele não foi projetado como um app para ouvir música
- Faltam funções essenciais como gerenciamento de playlists, ordenação por metadados e fila de reprodução
- Mesmo que a reprodução em si seja possível, a experiência geral do usuário é limitada, e há discussões relacionadas na comunidade da Apple
-
Apps de terceiros
- Na App Store há muitos apps para reprodução de música offline, mas muitos aplicam preços por assinatura até para tocar arquivos que o usuário já possui
- O Doppler tinha a vantagem de usar pagamento único
- Ainda assim, o UX era centrado no gerenciamento de álbuns, a qualidade da busca não era satisfatória e a importação do iCloud com muitas pastas aninhadas era lenta e difícil de usar
A mudança de React Native para SwiftUI
-
A escolha inicial por React Native/Expo
- Por experiências passadas com Swift, a ideia inicial foi evitá-lo e reaproveitar experiência de desenvolvimento web com React Native/Expo
- Implementar a UI de reprodução foi fácil, e foi usado o template music-player de Gionatha Sturba, que parecia ter os recursos necessários
- O problema cresceu no acesso ao sistema de arquivos e na sincronização de arquivos em nuvem
- Bibliotecas como
expo-filesystemsuportavam seleção básica de arquivos, mas a varredura recursiva de diretórios do iCloud profundamente aninhados frequentemente falhava ou derrubava o app - O sandbox do iOS impede que o app leia arquivos sem permissão explícita do usuário
- No fim, a abordagem baseada em JavaScript ficou mais complexa do que usar diretamente as APIs nativas da Apple, e migrar para Swift permitiu controlar melhor o acesso a arquivos do iCloud e as permissões do sandbox
-
Por que escolher SwiftUI
- Em vez de UIKit ou storyboards, foi escolhido SwiftUI para criar uma camada de UI declarativa e focar na lógica de domínio e na sincronização de dados
- A integração de
async/awaite Swift Actors no Swift facilitou o fluxo de dados e o gerenciamento de concorrência - O SwiftUI ajudou a estruturar o app em componentes independentes de ViewModel
- LLMs como OpenAI o1 e DeepSeek geravam código puro de UI ou binding de dados com menos interdependências complexas
Estrutura do app e modelo de dados
-
Três telas principais
- O app foi dividido em três fluxos: Library import, Library management e Player and playback
- Em Library import, adiciona-se a pasta da biblioteca no iCloud, escaneiam-se arquivos de áudio em todas as pastas e os caminhos são inseridos em um banco SQLite
- Library management serve para gerenciar as músicas adicionadas e montar playlists
- Player and playback cuida de repetição, shuffle, gerenciamento de fila e das funções de reproduzir, pausar e ir para a próxima faixa
- O seletor de arquivos padrão da Apple não se encaixa bem em um fluxo de selecionar de uma vez várias pastas e vários arquivos encontrados por palavra-chave
- Ao abrir o app com a biblioteca vazia, a aba Sync mostra o botão “Add iCloud Source”
- Ao selecionar uma pasta, uma barra de progresso é exibida durante a travessia da árvore, e quando a indexação termina o app vai para a aba Library
- Na tela inicial aparecem Playlists / Artists / Albums / Songs
- Ao tocar em uma faixa, surge um Mini-Player na parte inferior, e ao tocar nele abre-se o Player em tela cheia
- O Player em tela cheia oferece shuffle, repetição, reordenação da fila e volume
- Ao tocar em “+” em Sync para escolher outra pasta, novas músicas são mescladas em segundo plano sem necessidade de reiniciar
-
Camada de lógica no estilo servidor
- Apesar de ser um app mobile, foi aplicada uma arquitetura ao estilo backend com base em experiência prévia com web e backends em nuvem
- A camada de domínio/lógica foi separada das camadas de View e ViewModel para lidar de forma estável com sincronização em nuvem, parsing de metadados e acesso aos dados no SQLite
- A estrutura de camadas é a seguinte
- O SQLite armazena as linhas brutas das músicas e o índice FTS
- O Repository encapsula o banco de dados e expõe uma API assíncrona
- O Actor de domínio é dono das regras de negócio, como importação, busca e lógica da fila
- O ViewModel assina o Actor e converte os dados em structs para a UI
- A View em SwiftUI renderiza os dados recebidos
- As mudanças de estado permanecem thread-safe, e iCloud sync, reprodução e UI ficam separados para que não pulem camadas diretamente
Busca local com SQLite FTS5
- O armazenamento persistente usa SQLite, sem CoreData
- O motivo para evitar CoreData foi a necessidade de controlar diretamente o schema, queries brutas e principalmente a busca por texto completo
- Desde algo em torno do iOS 11, já é possível usar o SQLite com recurso FTS sem configuração adicional
- Para queries comuns foi usado SQLite.swift, mas as queries FTS foram tratadas com SQL comum
- O FTS5 do SQLite permite consultar metadados como nome de arquivo, artist, album e title sem infraestrutura de busca separada
-
Estrutura das tabelas FTS
- Foram usadas duas tabelas FTS5
songs_fts: usada porSQLiteSongRepository, indexaartist,title,album,albumArtistsource_paths_fts: usada porSQLiteSourcePathSearchRepository, indexafullPath,fileName- As duas tabelas FTS ficam ao lado das tabelas B-tree padrão
songsesource_paths - O FTS é somente leitura na UI, e toda escrita é tratada dentro do Repository
- No exemplo de criação de
songs_fts,songIdfica comoUNINDEXEDe é usado o tokenizerunicode61 unicode61foi escolhido para lidar com vários tipos de caracteres- Chaves que não são pesquisadas recebem
UNINDEXEDpara evitar crescimento desnecessário do dicionário de termos
-
Busca parcial e ranqueamento
- Quando o usuário digita “lumine”, internamente é adicionado um wildcard como “lumine*” para encontrar imediatamente resultados de busca parcial
- A ordenação dos resultados usa
bm25do SQLite - Essa abordagem oferece schema previsível, acesso local-first e busca por texto completo robusta, sem dependência de rede nem serviços externos
Acesso a arquivos no iOS e limitações de bookmarks
- Apps iOS podem salvar bookmarks persistentes para lembrar a localização dos arquivos
- Mas os security-scoped bookmarks, que concedem acesso estendido a arquivos fora do sandbox do app, só estão disponíveis no macOS
- Apps iOS podem lembrar caminhos com bookmarks comuns e solicitar acesso novamente pelo document picker, mas não há garantia de que esse acesso continuará silenciosamente ativo
- Há documentação relacionada da Apple em bookmark documentation
- Para amenizar isso, foi implementado um mecanismo alternativo que copia os arquivos para o próprio container sandbox do app
- Enquanto o bookmark é válido, os arquivos são copiados preventivamente em segundo plano
- Isso reduz o risco de acessar referências inválidas para arquivos de áudio caso o iOS redefina as permissões
- Essa abordagem também melhora a velocidade de indexação
- A estrutura de pastas é escaneada uma vez enquanto o acesso está ativo
- Só os arquivos de áudio relevantes são importados
- Diretórios profundamente aninhados podem ser percorridos com segurança
- O problema de reproduzir de forma estável arquivos de áudio individuais em locais externos após reiniciar o dispositivo não foi resolvido
- Mesmo em um app nativo, esse caso de uso ainda não é bem suportado no iOS, e tratar o acesso a arquivos de forma confiável continua complexo
Implementação da reprodução e da UI
-
Parsing de metadados
- Para fazer o parsing dos metadados dos arquivos de áudio, foi usado o framework AVFoundation da Apple com
AVURLAsset AVURLAssetpermite inspecionar metadados de arquivos de mídia como title e album artist- Alguns campos, como número da faixa, precisaram ser buscados diretamente nas tags ID3
- Como a documentação oficial não cobre bem os edge cases, exemplos de implementação foram encontrados via busca no GitHub
- Para fazer o parsing dos metadados dos arquivos de áudio, foi usado o framework AVFoundation da Apple com
-
Reprodução de áudio
- Depois da indexação da biblioteca, o player de áudio foi implementado inicializando instâncias de
AVAudioPlayerpara reprodução - Para controlar a música pela Central de Controle, foi necessário implementar o protocolo
AVAudioPlayerDelegatee conectá-lo aoMPRemoteCommandCenterda Apple - O
MPRemoteCommandCenterpermite que o app responda a controles de reprodução em nível de sistema
- Depois da indexação da biblioteca, o player de áudio foi implementado inicializando instâncias de
Experiência com Swift e desenvolvimento Apple
-
Dificuldades
- Os previews em tempo real do SwiftUI são um avanço no Xcode, mas a experiência geral de desenvolvimento ainda ficou abaixo do que o Flutter já oferecia há 5 anos em integração com VSCode, reload em tempo real no simulador e ferramentas de depuração familiares
- O suporte de LSP do Swift para Neovim ou VSCode exige ferramentas extras como
xcode-build-servere não equivale à experiência de desenvolvimento de ecossistemas centrados na web - Parte dos SDKs da Apple ainda permanece em estilo Objective-C
- A busca de arquivos via Spotlight só é exposta por
NSMetadataQuery, eNSMetadataQueryusa KVO e chaves em string - Ainda não existe um wrapper amigável para Swift
- A UI declarativa do SwiftUI é útil, mas o comportamento completo do app com permissões do iCloud envolvidas não pode ser emulado nos previews, então foi preciso mockar diretamente as interações com a nuvem
-
Pontos positivos
- Com
async/await, foi possível escrever código concorrente centrado em I/O de forma imperativa, sem callbacks - Também foi fácil colocar código síncrono dentro de Actors e chamá-lo como no ecossistema JavaScript
- Foi possível usar diretamente bibliotecas nativas da Apple, sem ficar limitado aos bindings open source de React Native ou Flutter
- Muitas APIs da Apple têm exemplos, o que facilitou começar
- A forma de compor UI do SwiftUI, parecida com React, trouxe produtividade e espaço para exploração
- Com
iOS dificulta usar por muito tempo apps pessoais
- Após cerca de 1 semana e meia de implementação, foi criado um player de música local/offline capaz de importar arquivos de áudio do armazenamento no iCloud
- Mas sem certificado de desenvolvedor, o app funciona por apenas 7 dias e depois precisa ser recompilado
- Para entrar no Apple Developer Program, é preciso pagar US$ 99
- Mesmo após o DMA Act da UE, o sideloading ainda não foi totalmente aberto
- Usuários da UE podem instalar apps de marketplaces de terceiros a partir do site do desenvolvedor
- Desenvolvedores ainda precisam entrar no programa de US$ 99/ano da Apple
- Também precisam aceitar os Alternative Terms da Apple
- Para uso pessoal ou hobby, a limitação de builds de desenvolvimento por 7 dias não desapareceu
- As PWAs no iOS também continuam limitadas
- Mesmo após as atualizações do iOS 16 a 18.x, PWAs continuam rodando dentro do sandbox do Safari
- WebGL2 e web-push estão disponíveis
- Web Bluetooth, USB, NFC, Background Sync e armazenamento garantido acima de cerca de 50 MB não são oferecidos
- Como o WebGL passa por uma camada de compatibilidade com Metal, a taxa de quadros real muitas vezes fica atrás de apps nativos em Metal
- A IA reduziu a complexidade do desenvolvimento de software ao facilitar o acesso ao conhecimento necessário para lidar com tecnologias pouco familiares, mas apps iOS ainda precisam seguir regras artificiais
- Mesmo para um app feito pela própria pessoa, ainda existe a barreira final da Apple para executá-lo por mais de uma semana, o que limita o desenvolvimento e a distribuição de apps pessoais
1 comentários
Opiniões no Hacker News
Passei pela época em que o Winamp era o player de música padrão e, mesmo na era do streaming, ainda mantenho minha biblioteca local de músicas organizada em pastas.
Como outras pessoas nos comentários aqui, criei como projeto de hobby um player de música à moda antiga para ouvir offline; é um app HTML/JS de uma página, com todos os controles por teclado e uma função simples de fila.
Dá para conferir em: https://nobsutils.com/mp
Eu gostava da simplicidade: um conjunto de arquivos dentro de um diretório, embaralhar a coleção inteira, tocar só um diretório específico.
Só um detalhe:
cmd + d, que hoje é o atalho padrão para favoritar uma página, muda o tema em vez de adicionar o site aos favoritos.Passei 25 anos montando uma coleção de música em formato FLAC e, no ano passado, comprei um celular Android e um cartão MicroSD de 1 TB para conseguir colocar todas as músicas nele.
Foi um projeto longo até a tecnologia alcançar isso, mas agora estou bem satisfeito por poder carregar tudo no bolso.
Não devo ser a única pessoa que não quer virar um locatário que abre mão do controle, fazendo streaming do que a indústria empurra e tolerando anúncios; e também é legal ver gente criando até o próprio app.
Com uma recodificação bem feita, dá para colocar todas as músicas em um cartão muito menor, com qualidade transparente em que não se ouve diferença, e manter os FLACs no desktop como backup.
Minha coleção passa de 3 TB mesmo com FLAC/APE/ALAC/WavePack sendo só cerca de 25%, então fica difícil ouvir música em movimento.
Não consigo escolher com antecedência o que colocar no dispositivo móvel.
A combinação de servidor https://www.navidrome.org com cliente https://symfonium.app funcionou muito bem.
Agora que já existem cartões SD de 2 TB, quando o preço baixar provavelmente vou comprar um.
Às vezes eu mudava e não aplicava, ou aplicava e de repente trocava para uma capa totalmente errada; pelo que parecia, era um bug do Android.
Fico curioso se você passou por esse problema.
Criei meu próprio app web para ouvir álbuns inteiros, mas podendo pausar no meio ou trocar de dispositivo.
Gosto de ouvir álbuns do começo ao fim, mas pelo menos o YouTube Music não lembra a posição de reprodução, e era incômodo ter que abrir o álbum de novo em outro dispositivo e procurar onde parei.
No meu app web, você cola a URL, o servidor baixa com
yt-dlpe dá para fazer streaming dali; ele sempre lembra a posição de reprodução, então posso ouvir no celular no carro e continuar no notebook no trabalho.Também funciona bem para adicionar mixes de outras fontes, como a NTS Radio.
Se esse app web for público, eu gostaria de dar uma olhada.
Ainda não li tudo, mas é um bom texto, e gosto de ler como o desenvolvedor tomou decisões mais detalhadas e por quê.
Concordo com a ideia de que os players de áudio que experimentei não me agradaram.
No mundo dos apps de música, a organização da tela e a UI parecem ter se tornado quase universais, mas eu não gosto disso e também não entendo muito bem.
Parece que estou lutando boxe com todos os apps de música, então fico contente em ver alguém tentando criar algo novo.
Ainda uso o app Apple Music com meus arquivos locais.
Deixo o serviço de streaming Apple Music desligado, importo tudo no app Apple Music do macOS e, como em 2007, conecto o celular ao notebook e sincronizo como se fosse um iPod.
Tudo funciona como esperado e, como minhas músicas não mudam com frequência, a sincronização não é um problema.
Também sinto uma nostalgia curiosa ao sincronizar por cabo.
É difícil concordar que, antes de existir
async/await, escrever código concorrente em Swift parecesse mais desajeitado e cheio de boilerplate do que em Go ou JS/TS.O processamento assíncrono pode facilitar a escrita de código concorrente, mas, conforme a base de código cresce, o raciocínio pode ficar menos simples.
Em uma base de código assíncrona complexa, fica mais difícil entender o fluxo do código e a concorrência.
Se o objetivo é reduzir o custo de execução de código em threads, existe a solução de green threads leves; se o objetivo é reduzir o custo de manutenção de código com threads, o assíncrono provavelmente exigirá mais esforço no longo prazo.
Só a encapsulação baseada em processos ou serviços já traz grandes benefícios.
Vejo isso menos como uma falha da concorrência em si e mais como uma limitação da abstração async/await.
Eu esperava que o texto fosse sobre um dispositivo físico, não só sobre software para gerenciar e tocar músicas
Alguns anos atrás, tentei comprar um MP3 player para meu filho de 10 anos, que adorava ouvir música, mas ainda não estava pronto para usar um celular, e fiquei surpreso com o quanto as opções eram terríveis
Acho que a Apple criou um grande vazio que ninguém conseguiu preencher quando descontinuou o iPod
O iPod shuffle em formato de pendrive USB foi o melhor MP3 player que já usei: era pequeno, podia ser conectado sem cabo separado e a bateria durava bastante
Não havia tela para navegar pelas músicas, mas essa era a ideia; era só deixar no shuffle
É uma pena que nem mesmo um conceito relativamente simples como esse tenha sido devidamente replicado no mercado de hardware
As pessoas vão dizer que o problema não é de hardware, mas de software/DRM, só que é realmente triste não existirem bons dispositivos portáteis baratos para tocar música
A existência do Spotify e dos smartphones matou os MP3 players, e os dois ocuparam tanto espaço no mercado que sufocaram todo o resto
Por exemplo, há https://www.fiio.com/cp13 e https://www.fiio.com/jm21
Também pode ser um pequeno projeto educativo interessante para aprender eletrônica e reutilização de dispositivos “antigos”
Há muitas opções de personalização externa, e modificações mais profundas, como adicionar Bluetooth ou USB‑C, também são possíveis
Como outras pessoas disseram, o lado do software também ainda funciona bem com o iTunes no Windows e a integração Finder/Music no macOS
Fabricantes chineses vendem por US$ 50 a US$ 100 dispositivos como Mini iPhone 16 ou Mini S24, que têm boa aparência, tocam música e também contam com funções de smartphone
É mais provável que os pais comprem dispositivos parecidos para os filhos em vez de MP3 players
Não dar um celular a um filho de 14 anos é um estilo de criação não convencional, e eu respeito, mas não parece haver demanda suficiente para ir além da oferta atualmente possível
Pode ser caro para uma criança de 10 anos, mas talvez dê para encontrar usado no eBay
A combinação de gerenciar música local com o Music.app e usar a sincronização do iPhone pelo Finder ainda funciona bem para mim
Ainda assim, o Music.app parece estar há anos sem manutenção desde a terrível reescrita do Big Sur, e também tem bugs irritantes
Apesar dos defeitos, essa combinação é a melhor solução de gerenciamento de biblioteca musical + sincronização móvel que já vi, mas como o software não é mantido e não é multiplataforma, no fim acho que vou ter que escrever meu próprio sistema
Ainda restam muitas esquisitices típicas do iTunes, como a janela modal de ajustes/preferências, um resquício da época do OS 9
Janelas de ajustes no OS X não deveriam ser modais
Suspeito que estejam trabalhando internamente em uma versão refeita do zero, como o Music para Windows baseado em WinUI, completamente novo
O mais irritante era a sincronização de podcasts; na época em que o iTunes cuidava disso, funcionava bem, mas na sincronização pelo Finder do Big Sur há um bug: se você vai para outra faixa ou podcast e depois volta, ele não lembra a posição de reprodução
Com isso, podcasts com mais do que alguns minutos ficam praticamente difíceis de usar no dispositivo
As pessoas realmente criam seu próprio app de música em vez de migrar para Android
Fico me perguntando se é por causa dos balões azuis, ou porque tudo “simplesmente funciona”, exceto tocar música offline
Não vejo qual é o problema em assar o seu próprio
Foi porque o app VLC não conseguia analisar de forma confiável os metadados dos FLAC no meu servidor de arquivos, e, por causa do limite de armazenamento, eu também não conseguia guardar toda a minha coleção de músicas no aparelho
Meu app é um protótipo no sentido de que quero mais recursos, mas ele já tem reprodução de áudio, acesso a arquivos remotos e análise de metadados FLAC, então funciona o suficiente para faltar motivação para implementar recursos adicionais
Como o autor, no começo eu queria usar React Native, porque já tinha experiência e alguns apps mantidos nele, mas não queria mirar outras plataformas nem depurá-las
Então decidi experimentar SwiftUI e, com uma ferramenta especial[1], criei algo parecido com hot reload
É um hack bem sujo que exige colocar flags de linker personalizadas no Xcode, mas funciona a ponto de eu não sentir falta da experiência de desenvolvimento de TypeScript e React Native
[1] https://github.com/johnno1962/InjectionIII
Na verdade, acho um pouco estranho que o autor não tenha encontrado um app de música gratuito de que gostasse
Ainda assim, ninguém precisa necessariamente de uma desculpa para criar algo; talvez tenha sido só a narrativa para começar a história
Também dá para manter as músicas escolhidas no aparelho
Se você tem uma coleção de músicas offline, recomendo muito https://play.google.com/store/apps/details?id=in.krosbits.mu...
Funciona muito bem, sem problemas
Também oferece suporte a Plex, Jellyfin, WebDAV, SMB etc.