Client MPD

Ma première app, c'est un client pour le serveur MPD ( Music Player Daemon https://www.musicpd.org ).
Pour avoir les couvertures il faut un serveur http qui les fournisse. J'utilise apache qui est intégré à toute les versions de MacOS.

L'avantage par rapport à iTunes c'est qu'il y a moyen de lire beaucoup plus de format de fichier (WavPack, DSD etc.).

Avant il y avait un seul client Mac, Cantata, mais le mainteneur a décidé de s'occuper uniquement de Linux et de lacher Windows et MacOS (certainement un disciple d'RMS).

Ca ressemble à ça:

J'ai mis l'app et le source en fichier joint.

Si y'a des bugs ou des améliorations à apporter au code je suis preneur. Ne pas oublier que je débute :p

Réponses

  • Bravo !

    Par contre les import de type

    #import "../../MPDService/MPDCache.h"

    c'est vraiment pas terrible, rajoute plutôt des répertoires Header Search Paths

  • klogklog Membre

    Félicitations !

  • PyrohPyroh Membre
    avril 2019 modifié #4

    Alors j'ai testé l'app. J'ai pas grand chose à tester parce que je n'ai pas de serveur MPD. Je peux juste dire que je n'ai jamais entendu un seul son sortir des web radios.

    Commence déjà par mettre ton code sur GitHub ou GitLab ça sera plus pratique.

    Concernant l'interface c'est pas mal à ceci près que tes assets relatifs au boutons de lecture paraissent flous. Soit tu les as redimensionné à la hache soit tu as voulu mettre une ombre, je ne sais pas, quoi qu'il en soit il ne faut pas (ni l'ombre, ni la hache).

    Pour tes vues de collection tu sors des guidelines Apple pour ce qui est du placement du tire et du bouton de retour sans pour autant proposer une alternative satisfaisante. D'ailleurs ce bouton retour fonctionne d'une manière bien singulière et déroutante pour l'utilisateur.

    Concernant le code y'a encore du travail. Rien que dans les modèles on sent que quelques subtilités de l'Obj-C t'ont échappé. Quand tu écris dans Stream.h :

    @interface Stream : NSObject {
        NSString *url;
        NSString *name;
        int position;
    }
    
    - (NSString *)url;
    - (NSString *)name;
    - (int)position;
    
    - (void)setUrl:(NSString *)aUrl;
    - (void)setName:(NSString *)aName;
    - (void)setPosition:(int)aPosition;
    
    @end
    

    On comprend vite que tu n'a pas compris à quoi servait @property

    Tu peux écrire ça de manière plus simple :

    @interface Stream : NSObject
    
        @property (nonatomic, strong) NSString *url;
        @property (nonatomic, strong) NSString *name;
        @property (nonatomic, assign) int positon;
    
    @end
    

    Et dans Stream.m tu auras plutôt :

    @implementation Stream
    
        @synthesize url;
        @synthesize name;
        @synthesize position;
    
    @end
    

    Aussi dans Album.h qui est défini un objet appartenant à la couche modèle on retrouve une référence NSButton *sender;. Si on veut faire du MVC propre le modèle ne doit pas avoir vent de l'interface et vice-versa.

    Mais c'est un bon début.

  • Pas tout regardé, mais, tu fais les setters/getters à la main, c'est étrange, mais comme l'a dit @Pyroh, @property serait utile ici.

    Et pour Stream, je n'aurais rien mis dans le .m, le @synthetize étant utile si tu réécris à la fois le getter et le setter, non ?

    Sinon, le [... isEqualToString:@"(null)"] est bizarre, à corriger sur le parsing sûrement.

    Quand je vois

    Song *s = [[Song alloc]init];
    [s setComposer:[GetTag song:song tag:MPD_TAG_COMPOSER]];
    

    J'aurais vu un custom init plutôt que de tout faire à la main ici.
    Genre, un init avec mpd_song plutôt.

    Intégrer SDWebImage de la sorte rend plus difficile des updates de code. CocoaPods ? Carthage ?

    Dans Préférences.m, Setting *settingPref; "flotte". Pas une var de la classe ?

    C'est un beau projet, ambitieux, mais c'est un projet fermé, précis, bon pour quelqu'un qui débute.

  • avril 2019 modifié #6

    @devulder a dit :
    Bravo !

    Par contre les import de type

    #import "../../MPDService/MPDCache.h"

    c'est vraiment pas terrible, rajoute plutôt des répertoires Header Search Paths

    Fait, merci.

    @klog a dit :
    Félicitations !

    Merci, c'est un garçon de 3,5Kg :D

    @Pyroh a dit :
    Alors j'ai testé l'app. J'ai pas grand chose à tester parce que je n'ai pas de serveur MPD. Je peux juste dire que je n'ai jamais entendu un seul son sortir des web radios.

    C'est normal si tu n'as pas MPD, c'est lui qui les lit.

    Commence déjà par mettre ton code sur GitHub ou GitLab ça sera plus pratique.

    C'est déjà le cas, j'ai voulu simplifier la vie.

    Concernant l'interface c'est pas mal à ceci près que tes assets relatifs au boutons de lecture paraissent flous. Soit tu les as redimensionné à la hache soit tu as voulu mettre une ombre, je ne sais pas, quoi qu'il en soit il ne faut pas (ni l'ombre, ni la hache).

    Ni l'un ni l'autre pourtant, ça vient d'ici https://material.io/tools/icons/ et directement téléchargé en 3 tailles diff. Chez moi c'est net.

    Pour tes vues de collection tu sors des guidelines Apple pour ce qui est du placement du tire et du bouton de retour sans pour autant proposer une alternative satisfaisante. D'ailleurs ce bouton retour fonctionne d'une manière bien singulière et déroutante pour l'utilisateur.

    On va y travailler.

    Concernant le code y'a encore du travail. Rien que dans les modèles on sent que quelques subtilités de l'Obj-C t'ont échappé. Quand tu écris dans Stream.h [...]

    On comprend vite que tu n'a pas compris à quoi servait @property

    C'est pas faute d'avoir dit que j'y connaissais rien :) merci pour l'exemples.

    Aussi dans Album.h qui est défini un objet appartenant à la couche modèle on retrouve une référence NSButton *sender;

    J'avoue que ce truc ne sert à rien et qu'il n'a rien à faire là O_ô, il devait être tard.

    Mais c'est un bon début.

    Merci :)

  • avril 2019 modifié #7

    @Larme a dit :
    Pas tout regardé, mais, tu fais les setters/getters à la main, c'est étrange, mais comme l'a dit @Pyroh, @property serait utile ici.

    Et pour Stream, je n'aurais rien mis dans le .m, le @synthetize étant utile si tu réécris à la fois le getter et le setter, non ?

    J'en conclu qu'il faut que je bosse sur ce sujet.

    Sinon, le [... isEqualToString:@"(null)"] est bizarre, à corriger sur le parsing sûrement.

    C'est un retour d'MPD (de libmpdclient plus précisément), y'a pas de parser entre deux.

    Quand je vois

    Song *s = [[Song alloc]init];
    [s setComposer:[GetTag song:song tag:MPD_TAG_COMPOSER]];
    

    J'aurais vu un custom init plutôt que de tout faire à la main ici.
    Genre, un init avec mpd_song plutôt.

    Je te fais confiance et vais faire de la sorte alors.

    Intégrer SDWebImage de la sorte rend plus difficile des updates de code. CocoaPods ? Carthage ?

    Pas vraiment, il a déjà subit un bonne cure d'amaigrissement (que je n'ai pas terminé).
    J'aime pas trop avoir des dépendances avec des trucs externe, ça me rend dépendant et j'aime pas ça. Je préfère l'intégrer, supprimer tout ce qui ne me convient pas et de maintenir le p'tit bout qui reste par moi même.

    CocoaPods j'ai testé et ça fait un poil "usinagaz" à l'utilisation. En plus les 3/4 des truc qui sont dessus sont pour iPhone ce qui ne m'intéresse pas du tout. Ce phone étant complètement verrouillé c'est certain que j'en aurais jamais.

    Dans Préférences.m, Setting *settingPref; "flotte". Pas une var de la classe ?

    C'est le premier fichier que j'ai créé en recopiant des exemples ici et là donc je vais me repencher dessus.

    C'est un beau projet, ambitieux, mais c'est un projet fermé, précis, bon pour quelqu'un qui débute.

    Ambitieux je sais pas, fermé je comprend pas le sens, précis oui, pour débuter je me suis dit la même chose :D

    Merci ;)

  • Fermé, c'est dans le sens où tu as un but précis, tu ne te perds pas à vouloir faire ci/ça, te perdre à droite à gauche.
    C'est toujours le risque dans n'importe quel projet, encore plus quand on est seul.

  • Ok, oui effectivement c'est fermé :)
    Je fais attention de ne pas partir dans des trucs tordu ou qui ne me serviront jamais, je garde bien à l'esprit que je suis tout seul donc mise à jour et debug c'est pour ma pomme.
    C'est quand même plus simple d'insulter des dev quand ça marche pas... o:)

  • @Larme a dit :
    Fermé, c'est dans le sens où tu as un but précis, tu ne te perds pas à vouloir faire ci/ça, te perdre à droite à gauche.
    C'est toujours le risque dans n'importe quel projet, encore plus quand on est seul.

    C'est un mal ça ? Tu parle de risque mais je ne vois pas en quoi c'est négatif.

  • En tout cas, avec vos conseils mon code a fait une sacrée cure d'amaigrissement...

    Déjà plus de 300 lignes qui ont sauté et c'est pas fini O_o

  • devulderdevulder Membre
    avril 2019 modifié #12

    Objective-c c'est nul, lent, et energivore :)

    Voila un équivalent swift https://github.com/danbee/persephone

  • klogklog Membre
    avril 2019 modifié #13

    @devulder a dit :
    Voila un équivalent swift https://github.com/danbee/persephone

    J'adore la franche camaraderie et l'esprit de soutien qui règnent ici :(

  • @klog a dit :

    @devulder a dit :
    Voila un équivalent swift https://github.com/danbee/persephone


    J'adore la franche camaraderie et l'esprit de soutien qui règne ici :(

    Si il est vrai qu'une petite pique en direction de notre ami @Harlo concernant le langage utilisé est de bon ton, @devulder est allé un peu loin sur le coup-là...

    D'autant que persephone a l'air un peu en deçà de ce que propose @Harlo...

  • @Pyroh a dit :

    @Larme a dit :
    Fermé, c'est dans le sens où tu as un but précis, tu ne te perds pas à vouloir faire ci/ça, te perdre à droite à gauche.
    C'est toujours le risque dans n'importe quel projet, encore plus quand on est seul.

    C'est un mal ça ? Tu parle de risque mais je ne vois pas en quoi c'est négatif.

    Le risque c'est de ne jamais terminer ton projet. Cela dépend des personnalités, mais c'est souvent le risque sur les projets sans deadline (la contrainte de temps influe sur la contrainte de buts)

  • @klog a dit :

    @devulder a dit :
    Voila un équivalent swift https://github.com/danbee/persephone


    J'adore la franche camaraderie et l'esprit de soutien qui règne ici :(

    Le lien a été mis dans le but qu'il voit comment d'autre personne font une app similaire avec surement une autre approche (modèle, langage) dans la mine d'or qu'est Gihub, rien de plus.

  • Hop, une tite mise à jour du code en suivant vos conseils :)

    Pref et setting étaient vraiment très très crade en fait >_<

  • LarmeLarme Membre
    avril 2019 modifié #18

    Tu ne veux pas mettre ça sur un Repo Git plutôt ? Cela permettrait de voir les progressions et se concentrer sur les modifs.

  • avril 2019 modifié #19

    Version 1.2, meilleurs gestion des playlist, code qui devrait être moins porkz :p
    Playlist au click droit en attendant de maitriser le drag&drop.
    Amélioration du code avec codebeat, ça vaut ce que ça vaut...

    https://github.com/Old-Geek/MPD-Client/releases/tag/v1.2

  • J'ai cloné, et j'ai regardé un peu ton code.
    Premièrement, je n'aurais pas fait autant de fonctions, mais des méthodes, j'ai l'impression d'être en C. Mais t'as une partie C, alors cela peut se comprendre.

    J'ai trouvé ce p'tit bout de code:

    - (void)send:(struct mpd_connection *)mpdConnection {
        assert(mpd_connection_get_error(mpdConnection) != MPD_ERROR_SUCCESS);
    
        NSString *error =  [NSString stringWithFormat:@"%s", mpd_connection_get_error_message(mpdConnection)];
        NSString *description = @"pas d'information complémentaire";
    
        if ([error isEqualToString:@"Connection closed by the server"]) {
    
            error = @"Connexion fermée par le serveur";
            description = @"Le serveur MPD ne répond plus.\n\n Vérifiez qu'il n'a pas été tué et que la connexion n'a pas de problème.";
    
        } else if ([error isEqualToString:@"Connection reset by peer"]) {
    
            error = @"Réinitialisation de la connexion";
            description = @"1. Vérifiez que le serveur MPD est joignable et le service toujours actif.\n\n2. Vérifiez que la connexion réseau est fiable.";
    
        } else if ([error isEqualToString:@"Connection refused"]) {
    
            error = @"Connexion rejetée";
            description = @"1. Vérifiez que le serveur est joignable et que MPD est actif.\n2. Vérifiez qu'un firewall ne bloque pas la connexion.\n3. Vérifiez votre connexion réseau.";
        }
    
    
        if (error != nil) {
            MPDError *mpderror = [[MPDError alloc] init];
            [mpderror setError:error];
            [mpderror setErrorDescription:description];
            [self.delegate onError:mpderror];
        }
    }
    

    Avec:

    @interface MPDError : NSObject
    
    @property (nonatomic, strong) NSString *error;
    @property (nonatomic, strong) NSString *errorDescription;
    
    @end
    

    Et ceci :

    enum mpd_error {
        /** no error */
        MPD_ERROR_SUCCESS = 0,
    
        /** out of memory */
        MPD_ERROR_OOM,
    
        /** a function was called with an unrecognized or invalid
            argument */
        MPD_ERROR_ARGUMENT,
    
        /** a function was called which is not available in the
            current state of libmpdclient */
        MPD_ERROR_STATE,
    
        /** timeout trying to talk to mpd */
        MPD_ERROR_TIMEOUT,
    
        /** system error */
        MPD_ERROR_SYSTEM,
    
        /** unknown host */
        MPD_ERROR_RESOLVER,
    
        /** malformed response received from MPD */
        MPD_ERROR_MALFORMED,
    
        /** connection closed by mpd */
        MPD_ERROR_CLOSED,
    
        /**
         * The server has returned an error code, which can be queried
         * with mpd_connection_get_server_error().
         */
        MPD_ERROR_SERVER
    }
    

    Je vais passer le fait que tu préfères envoyer directement un message en français.

    Tu te bases sur la description pour ton erreur, or, il faudrait se baser sur son code. Et de la même manière où je t'avais suggéré de faire un initWith, pareil ici.

    -(id)initWithMPDConnection:(struct mpd_connection*)mpdConnection
    {
        self = [super init];
        if (self)
        {
            enum mpd_error errorCode = mpd_connection_get_error(mpdConnection);
            switch (errorCode) {
                case MPD_ERROR_SUCCESS:
                    break;
                case MPD_ERROR_TIMEOUT:
                    self.error = @"";//...
                    break;
                default:
                    break;
            }
    
        }
        return self;
    }
    

    Après, tu as déjà une classe NSError, à toi de l'utiliser plutôt pour ce besoin, en créant une extension plutôt.

    +(NSError *)errorFromMPDConnection:(struct mpd_connection*)mpdConnection; Elle contient déjà un champ code, error message, localized message, recovery suggestion, etc.

  • @Larme a dit :
    J'ai cloné, et j'ai regardé un peu ton code.
    Premièrement, je n'aurais pas fait autant de fonctions, mais des méthodes, j'ai l'impression d'être en C. Mais t'as une partie C, alors cela peut se comprendre.

    En fait j'utilise les fonctions plus pour me simplifier la vie: fonction( ) = externe, methode: = inclus
    Vu que ça change rien au fonctionnement du logiciel j'en profite et c'est uniquement pour les menus contextuel. Je changerais p'têt ça par la suite.

    Pour la gestion des erreurs c'est surtout une ébauche et j'ai encore des trucs à tester de ce coté, je ne savais pas encore comment tourner ça mais tu m'as donné la piste :)

    Merci beaucoup pour tout ces conseils qui seront appliqué :)

  • LarmeLarme Membre
    avril 2019 modifié #22

    C'est toujours plus facile de faire de la review. On ne code pas vraiment, on fait avec des "si...". On lance des pistes quoi.
    Et si j'peux aider en pointant vers deux/trois trucs, ça m'va.

    Pour tous tes inits, sur iOS, on a tendance à faire, par convention :

    -(id)init...
    {
        self = [super init]; //Ou autre super init méthode à adapter
        if (self) 
        {
            //Init des properties
        }
        return self;
    }
    

    Je ne sais pas si sur macOS on ne le fait pas (mais ça m'étonne vu qu'il y a pas mal de logique similaire), mais je ne t'ai pas vu le faire.

  • Apparement si, d'après ce que j'ai pu trouver il faut aussi le faire sur mac.
    C'est flippant ce truc, parfois ça peut foirer, retourner de mauvaises valeur, provoquer des mem leaks voir crash...
    De la part d'apple, ça fait quand même un poil bricolo O_o

  • CéroceCéroce Membre, Modérateur

    @Larme a dit :
    Je ne sais pas si sur macOS on ne le fait pas (mais ça m'étonne vu qu'il y a pas mal de logique similaire), mais je ne t'ai pas vu le faire.

    Si, pareil sur macOS, ça a toujours été ainsi en Objective-C.
    En pratique, -[NSObject init] ne renvoie jamais nil.

  • CéroceCéroce Membre, Modérateur

    @Harlo a dit :
    C'est flippant ce truc, parfois ça peut foirer, retourner de mauvaises valeur, provoquer des mem leaks voir crash...
    De la part d'apple, ça fait quand même un poil bricolo O_o

    Je ne saisis pas trop de quoi tu parles. En théorie +alloc peut ne pas fonctionner (mémoire pleine).
    Ensuite, même en ayant ARC d'activé (sans doute le cas sur ton projet), on n'est jamais à l'abri des leaks et des zombies. Ce n'est pas Apple qui travaillerait mal; quel que soit le langage, la mémoire doit toujours être gérée d'une manière ou d'une autre.

  • avril 2019 modifié #26

    Gestion des erreurs corrigé :)

    En 3 catégorie, erreur MPD, erreur Serveur MPD et erreur système mais sans passer par une extension de NSError:

    NSError *error = [NSError errorWithDomain:NSPOSIXErrorDomain
    code:mpd_connection_get_system_error(mpdConnection) userInfo:nil];
    self.errorName = @"Erreur système";
    self.errorDescription = [NSString stringWithFormat:@"%@", [error localizedDescription]];

  • Le code de NSImage* sidebarIcon(NSString *string) pourrait être ainsi :

    NSDictionary *items = @{@"Library": @"sidebar_album",
                            @"Queue": @"sidebar_queue",
                            @"Stream": @"sidebar_stream",
                            @"Radio": @"sidebar_radio"};
    NSString *imageName = items[string] ? items[string] : @"sidebar_playlist";
    return [NSImage imageNamed:imageName];
    

    plutôt que :

    NSArray *items = @[@"Library", @"Queue", @"Stream", @"Radio"];
    
    int item = [items indexOfObject:string];
    switch (item) {
        case 0: // Library
            return [NSImage imageNamed:@"sidebar_album"];
            break;
        case 1: // Queue
            return [NSImage imageNamed:@"sidebar_queue"];
            break;
        case 2: // Stream
            return [NSImage imageNamed:@"sidebar_stream"];
            break;
        case 3: // composer
            return [NSImage imageNamed:@"sidebar_radio"];
            break;
        default:
            return [NSImage imageNamed:@"sidebar_playlist"];
            break;
    }
    
  • Les p'tits trucs bien sympa qui te font voir les choses avec un angle qui ouvre d'autres possibilités, j'adore :)

    Merci :D

  • avril 2019 modifié #29

    v1.6
    https://github.com/Old-Geek/MPD-Client/releases

    Consomme moins de:

    • HDD (cache)
    • RAM
    • CPU

    Surveillance des playlists, streams pour se mettre automatiquement à jour si supprimé à partir d'un autre client.
    Amélioration de la fonction recherche (qui est plus un filtre sauf pour la recherche de radio).
    Amélioration de l'affichage de tout ce qui est image/icon (y compris en mode dark).
    Changement de l'icon d'application, le même que MPD officiel mais revisité (quand on est pas un designer, on l'est pas :p )

    Moins de bug, code plus propre ?

Connectez-vous ou Inscrivez-vous pour répondre.